Once again, you hit the nail on what I'm currently doing..
Awesome thanks Ryan!
It's a good way of accomplishing that kind of situation. However, I'd rather suggest what you mentioned first: split the various fields into fieldsets and use Javascript; sounds much easier, the same visual process is achieved and the Rails code is kept clean.
In either way, you wanted to present a 100% Rails way of accomplishing it and you did. Nice!
Josę: right, but when using javascript you can only do some simple js validation for a fields. For example like above - order; you cannot check actual stock, promotions etc.
First time in 217 episodes that you are too late for me :)
Thanks for the awesome episode. Really a smart way for handeling this. Much cleaner than i did :)
Thanks!
Great Episode. I'd suggest using with_options when having multiple validations on different steps. This would look like this http://pastie.org/994652
First time you're screencast comes far too late. Yet i must admit that you approach is better by far. again.
Nice for a "first attempt", but it really needs refactoring or separation of concerns. I don't like how both the model and controller are essentially providing view functionality.
I think this could be quite easily plugin/gem-ified with a Module which alters the common use case of if/else on #save. That is, an alias_method_chain on #save which is altered to return false if there are outstanding steps (where steps are defined via methods made available through said module).
@Artur: Sure you can, it is called AJAX :-)
Therefore in the "real world" I'd go for the JS-based solution, no need to complicate the server side.
great episode Ryan! Another alternative to this would be to use the Acts As State Machine
I agree with @Marc since there are a few MVC "violations" here. In particular there is a lot of controller-specific functionality imbedded in the model which would make it very difficult to re-use the model with a different controller/view e.g. taking an order via a webservice. But as a practical solution it works.
Keep it up, I look forward to my Monday coffee break.
Type-O in your final method: 'all?' Should be 'each'
Nevermind: my mistake -- your ruby-Fu is > Mine :-)
recently I had to do the exact same thing, only I tackled it slightly differently
instead of using :if on the validations I just check to see if a model is valid based on select attributes
the code is here if anyone is interested in the approach
http://gist.github.com/429048
and yes, most of that controller code should be pushed into the model
Thanks for this! Very well planed as always, you really know your way arround rails code!
Hey Ryan,
I've finally caught up on Railscasts! These are awesome. I just wanted to let you know that the recent videos haven't been showing up very well using VLC on Ubuntu. I don't know what you are doing differently but it started around ep. 212.
i would love to see an episode about refactoring these actions, i always keep ending up with very long actions.. :/
Yeah... I don't know...
Store form fields in session and keep view states in model is weird.
Anyway, it works.
You could simplify the model code by using my grouped_validations plugin to break up the validation into step groups http://github.com/adzap/grouped_validations
You can then just define
validation_group :shipping do
validates_presence_of :shipping_name
end
and in the controller
@order.group_valid?(session[:order_step].to_sym)
Nice epsiode.
I kind of get the whole 'mvc violation' thing, but I still like your solution. Like you said it could use some refactoring but it's really not that bad. The whole skinny controller thing is a bit ridiculous and people go a bid overboard IMHO.
I've often wondered how I would achieve this, but have never tried it because I too like the 'one form' approach. I tend to ask for as little information is required to complete the transaction, then ask for more when you need later, it appears to be less invasive to one's privacy.
I don't think the JS only solution is a good one. The wizard should be able to fall-back to Rails; when JS is not available. And doing validations in JS could mean crappy data being stored if JS was turned off.
what about wizardly (http://github.com/jeffp/wizardly) and actsaswizard (http://github.com/adkron/actsaswizard) ?
Indeed a little refactorization would be great.
Thanks this is the kind of episodes that I learn a lot of how Rails works.
*Thank you* for exploring this, even if it is literally a couple of years too late for me -- I could have used this back in 2007 and 2008! But it helps show how to avoid and/or clean up some of the egregious code I perpetrated back then, in my ignorance...
Would we not have problems using the session, if the user opened a new window and wanted to edit two products at the same time?
Instead of storing the variables in the session, we could equally store them in hidden fields. We could create another model object called old_product, and then store its properties in hidden fields in the form.
When I started learning RoR this was the first project I had but I went a different route that took way longer than it should have and way to much code. This is completely simplified compared to what I have. I'll have to implement this method of the multi-step form. Thanks for doing this. Looking forward to the refactoring.
Re: using hidden form fields as an alternative to the session:
To avoid duplicating the list of fields, I've been playing with passing *one* hidden field, compressed_order_params, containing a gzipped yaml dump of any previous parameters.
So far, I'm liking it, but these are early days. :-)
ActiveSupport provides good help. Compressing the parameters looks something like the following, with decompressing pretty much the mirror image:
ActiveSupport::Base64.encode64(
ActiveSupport::Gzip.compress(
hash.to_yaml
)
)
Hello Ryan,
Its good to use workflow + validatable for such things. Much cleaner.
http://github.com/geekq/workflow
@RaynB this is a nice way of doing a multistep form in rails, but your method is missing unique urls for each step. The urls are needed in order to get funnel tracking working correctly.
I know that this is not Rails specific, but would be required for a real world app.
Anyway, keep the Railscasts coming :-)
Ryan,
you have NO idea how long I've been waiting for an explanation for how to do this. I don't think I would've ever figured it out on my own. Thank you so much!
Hi Ryan,
Great episode. For a production app, I suggest more investigation of storing the partially completed form in the model.
The issue is that "abandonment" is common with multi-step forms. If the partial information is stored, analysis can be done to discover when and how often the form is being abandoned.
Re @dkjess and funnel analysis--you can usually call your analysis system explicitly via javascript and tell it which funnel step the page is currently showing.
Do you really need self in self.current_step? Would this not work if you simply assign to current_step?
Sorry if I am missing something very basic.
I'm getting an error when using deep_merge! it is assuming that the sessions hash is nil for some reason. I'm getting this error:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.deep_merge!
The code is like this:
def new
session[:user_params] ||= {}
@user = User.new
end
def create
session[:user_params].deep_merge!(params[:user]) if params[:user]
logout_keeping_session!
@user = User.new(session[:user_params])
@user.current_step = session[:user_step]
if params[:back_pressed]
@user.previous_step
else
@user.next_step
end
session[:user_step] = @user.current_step
render "new"
end
Can anyone help?
Great! That was a complete tutorial from A to Z. Good job!
session? very bad way, one textarea field that user can put a lot of text and cookie session broken.
hidden fields is more safe way (but it not work like and session if upload need)
(params[:order]||{}).each_pair do |k,v|
hidden_field_tag "order[#{k}]", v
end
at bottom or top of form, I'm not remeber params parsing order
I generally do something similar, but prefer to wrap all the workflow logic into separate module/classes that just check the state of one or more model objects.
For example you might have an OrderWorkflow class which has a #steps method that returns an array of FirstStep/SecondStep etc. objects. Each step object implements a complete? method & the OrderWorkflow#current_step simply returns the first object that is not complete.
One advantage of that is that you can take advantage of rail's partial rendering on objects to provide different views for each step.
Ryan,
I was going throught this post
http://blog.envylabs.com/2009/08/the-rails-state-machine/
ruby has state machine built in the models,
Any particular reason you didn't use it, also if you can cover it in later screencasts.
Thank you! Thank you! Thank you! Exactly what I needed when I needed it!
+1 vote for a refactored controller, if you want to bang out a quickie. ;) Also, I think it might be a good idea to show storing the params in the DB and using the update action rather than sessions when you do the refactored controller.
Thanks again! You're the best!
Hi Ryan , many thanks for this.
I get it for simple model. But I can not figure out how to do with HABTM model.
thanks again
Thank you so much, it was just what I was looking for!
Although I have so problems that I had fix:
- didn't recognized <% title "New Order" %> so I had to run script/generate nifty_layout
- I got this error:
Missing template shipping_step.erb in view path .../app/views:
so I have to put <%= render :partial => "#{@order.current_step}_step", :locals => { :f => f } %> in new.html.erb
- render "new" didn't work for me either, changed to ender :action => 'new'
-for first_step? and last_step? : self.current_step, didn't work with only current_step
I experience some strange behaviours when playing around with this multistep form. Sometimes, when pressing back to change a step, it jumps two steps forward when pressing next afterwards. Somebody else who sees this in his form?
Very nice way of doing that kind of stuff.
Thanks!
Hi, i'm trying to lern rails. In this episode there where some lines of code which I didn't understand. Can anyone give me link to a manual or tutorial where I can read about following lines of code?
1:
<%= render :partial => "#{@order.current_step}_step" %>
What does #{} mean? Is it ruby syntax?
2:
%w[shipping billing confirmation]
I'd like to know why there is a % sign and why there are no "," between these entries.
wouldn't it be something like:
steps = [shipping, billing, confirmation] ??
I'm really at the beginning and sometimes having problems to figure out if anything new is ruby, rack, rails and so on.
So links to specific manuals would be nice.
Thanks for these episodes!
Much love from Germany
I am very appreciate the issue that it can benefit to the low income guys, thanking for your sharing, it is wonderful.
It's one of the most important facto to fix this time.
@Christian
the #{} is a ruby way to return the content of the code into a string
so
" 1+1=#{1+1} " retorna " 1+1=2 "
the %w[shipping billing confirmation] is another ruby way to define an array of strings. Is the same of ["shipping" , "billing" , "confirmation"]
Hope it helped
I like this approach, but one problem is that it breaks the browser forward/back/reload buttons. So for instance, clicking continue will take you forward one step, and then clicking 'reload' will then mistakenly take you forward another step. This can be solved by storing the current step in the view rather than the session - I've shown the changes here: http://pastie.org/1084054
Also, should validation really be done when clicking the back button?
Ryan, could you show the view that is calling this template?
Great article written on my life, help, http://www.cmonc.com Thank you for your article, hope to become friends.
I recently came across your blog and have been reading along.
I thought I would leave my first comment. I don’t know what to say except that I have enjoyed reading.Nice blog,I will keep visiting this blog very often.
love converse all star,love yourself.High quality low price.It's fit for you.
I experience some strange behaviours when playing around with this multistep form. Sometimes, when pressing back to change a step, it jumps two steps forward when pressing next afterwards. Somebody else who sees this in his form?
David Heinemeier Hansson..thanks url=http://www.icheapwholesale.com]cheap wholesale clothes[/url],,
Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it. My readers have about the same interets
You have NO idea how long I've been waiting for an explanation for how to do this. I don't think I would've ever figured it out on my own. Thank you so much!
Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it.
Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it.
For example like above - order; you cannot check actual stock, promotions etc.
This is really a nice guide for Newbies like me. Thank you.






