Rails controller : How to pass parameters between actions in wizard style? - ruby-on-rails

I'm trying to build a wizard style site to help creating an object.
Each step user inputs some parameters. I want to create and persist the object in DB only when all parameters are submitted at the final step.
class ExperimentsController < ApplicationController
def wizard_step_1
render template: "experiments/wizards/step1"
end
def wizard_step_2
render template: "experiments/wizards/step2"
end
def wizard_step_3
binding.pry
# how to access params in step 1 & 2 here?
end
end
View step1 below:
<%= form_tag(action: 'wizard_step_2') do %>
<%= text_field_tag("user_name") %>
<%= submit_tag("Next >>") %>
<% end %>
View step2 below:
<%= form_tag(action: 'wizard_step_3') do %>
<%= text_field_tag("user_email") %>
<%= submit_tag("Next >>") %>
<% end %>
I didn't put routes info here as it's tested OK. When user visit step1 and input user name, when submitting form control goes to wizard_step_2 and show view step2. Here user inputs email and when clicking submit button execution goes to action wizard_step_3.
Then my question is how to obtain user name and email the user inputs in previous steps?
I'd thought about gem Wicked, and Rails cache etc., and more or less they don't fulfill my needs. Is there a decent way doing this?

I don't have huge experience with this, but you may be able to benefit from: Wizard Forms Railscast
I'd imagine this type of setup will save the step 1, step 2, etc params as session variables:
class ExperimentsController < ApplicationController
def wizard_step_1
render template: "experiments/wizards/step1"
end
def wizard_step_2
render template: "experiments/wizards/step2"
#Create session vars from step1
session[:user][:name] = params[:user_name]
end
def wizard_step_3
binding.pry
#Step2 is already passed as params ;)
params[:user_name] = session[:user][:name]
end
end
View step1 below:
<%= form_tag(action: 'wizard_step_2') do %>
<%= text_field_tag("user_name") %>
<%= submit_tag("Next >>") %>
<% end %>
View step2 below:
<%= form_tag(action: 'wizard_step_3') do %>
<%= text_field_tag("user_email") %>
<%= submit_tag("Next >>") %>
<% end %>
I know this example is not very DRY, but it's the basis of what I'd start to look at. Essentially, you need to be able to persist data between instances of objects, which lends itself entirely to sessions

Related

Rails: How to check which form in a view was submitted

There are 2 forms on one page. I want an if else statement in the controller to use different params and variable values depending on which form was submitted. From doing a google search the best I came across was to have a value on the submit button.
<%= f.submit "Save" :value => "x" %>
If this is a plausible way I cant find how to make an if else statement for checking if the submit value is 'x'.
Something like
if submit.value == 'x'
do something
else
do something else
end
Really not sure. If there is another way of having an if else statement in the controller to catch witch form was submitted by using an id or name or whatever I'm happy to hear it.
The value of the submit button can be accessed with the help of params[:commit], so you can use it to check which form is submitted.
if params[:commit] == 'x'
do something
else
do something else
end
#Pavan has the direct answer, however if you're evaluating form submissions by their respective submit values, you've got a major issue with your pattern.
Form
Forms should be a way to pass values to your controller, which will then populate the model. You should not have to determine actions based on those values, unless you have different functionality...
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= form_for post do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
<% end %>
The above will create multiple forms, all submitting to the posts#update method:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
#post = Post.find params[:id]
#post.update post_params
end
private
def post_params
params.require(:post).permit(:x, :y, :z)
end
end
The values inside those forms don't matter, nor to which post object they were sent; they will all be evaluated in exactly the same way.
--
Actions
The other way around this is to make separate actions for the different forms:
#config/routes.rb
resources :posts do
patch :update_2, on: :member
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update_2
#post = Post.find params[:id]
#post.param = "value"
#post.update post_params
end
end
#app/views/posts/show.html.erb
<%= form_for #post, url: posts_update_2_path(#post) do |f| %>
<%= f.submit %>
<% end %>
You could use something like
<%=f.submit "Basic update", name: "basic-update" %>
<%=f.submit "Security update", name: "security-update" %>
and then check in your controller:
if params.has_key? "security-update"
#do something
elsif params.has_key? "basic-update"
#do another thing
end

Interpolating data in a rails view

I'm a beginner at rails and thus far interplating data in views has been pretty straight forward. I've been introduced to something slightly new as far as how the controllers are setup and as a result I'm not sure how one would go about getting the data to present in the view.
First controller
class PagesController < ApplicationController
def index
#guestbook_entry = GuestbookEntry.new
render "welcome"
end
end
Second controller
class GuestbookEntriesController < ApplicationController
def create
GuestbookEntry.create(guestbook_entry_params)
redirect_to root_path, notice: "Thank you for your entry."
end
private
def guestbook_entry_params
params.require(:guestbook_entry).permit(:body)
end
end
And here is the welcome.html.erb
<h1>Welcome to My Guestbook</h1>
<br>
<%= image_tag("under_construction.gif") %>
<div id="guestbook-entries">
<p>Guestbook Entries:</p>
<ul>
</ul>
</div>
<%= form_for #guestbook_entry do |f| %>
<%= f.label :body, "Guestbook Entry:" %>
<%= f.text_area :body %>
<%= f.submit "Submit" %>
<% end %>
So it wants me to iterate through all the entries and display them on a welcome page that's located in view/pages/welcome.html.erb.
Up to this point I guess I've only been doing basic simple rails applications where the view corresponded with the controller, and followed the typical CRUD setup, where index would hold the #xxx = Xxxx.all and new/create would handle #xxx = Xxxx.new/create/build. I thought I could simply move the PageController's index action to create/new and do
def index
#guestbook_entry = GuestbookEntry.all
render "welcome"
end
To satisfy the test (it looks for render welcome in the index action)
This seems weird but again I admit, I'm a beginner.
If you want to list all the guest book entries on your root page you would do something like:
class PagesController < ApplicationController
def index
#guestbook_entry = GuestbookEntry.new
#guestbook_entries = GuestbookEntry.limit(10).all
render "welcome"
end
end
And in your view you would list them like:
<% if #guestbook_entries.any? %>
<div id="guestbook-entries">
<p>Guestbook Entries:</p>
<% #guestbook_entries.each do |entry| %>
<ul>
<li class="entry"><%= h(entry.body) %></li>
</ul>
<% end %>
</div>
<% end %>
The rest of you application is correct - you should be creating entries in GuestbookEntriesController#create. In many real life applications then the functionality of the standard new and edit actions can actually be a totally different controller.

How to pass parameters to a controllers in ROR

I want to pass year and month to a controller. When the submit button is pressed, the year and month info should be passed to the action show of the controller foo.
I have edited my form as
<%= form_tag "/test" do %>
<%= month_field_tag :yearMonth %>
<%= submit_tag %>
<% end %>
How should I do to archive this in a view and how to get the passed parameter in foo controller?
Rails 4+ You must have a params.require private def in your controller to permit the variables you define as "okay" to be parsed. Your controller will filter out anything not specifically allowed via the .require.
private
def name_of_controller_params
params.require(:name_of_controller).permit(:yearMonth, :anothervar, :etc)
end
To see the params hash in your controller at the action point, you can insert this debug code into your relevant action. You can use this to see if your param is making it from the form into your controller's action.
def show
flash[:info] = "Params hash #{params}."
other actions...
end
Something like this will work:
<%= form_tag '/test' do %>
<%= month_field_tag 'month_value', '' %>
<%= submit_tag %>
<% end %>
Then inspect params from show action to see if month_value is there.

Rails: Why isn't the 'create' action saving the newly created Quiz instance?

My form gets passed a 'new' Quiz (not saved to the database). My form partial looks like this:
<%= form_for(#quiz) do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%= f.check_box(:answer2) %>
<%= f.check_box(:answer3) %>
<%= f.check_box(:answer4) %>
<%= f.check_box(:answer5) %>
<%= f.check_box(:answer6) %>
<%= f.check_box(:answer7) %>
<%= f.check_box(:answer8) %>
</p>
<p>
<%= f.submit("Get my results!") %>
</p>
<% end %>
Here is my QuizzesController#create action:
def create
#results = Quiz.create(post_params) #from private method
if #results.save
redirect_to results_path
else
#error handle here
end
end
...which gets triggered when the user clicks 'get my results' on my quiz form. And the post_params method looks like this:
def post_params
params.require(:quiz).permit(:id, :user_id, :answer1, :answer2, :answer3, :answer4, :answer5, :answer6, :answer7, :answer8) #add other attributes here
end
My results/index.html.erb looks like this:
<div class="container">
<!-- Example row of columns -->
<div class="row">
<h1>Results</h1>
<p><%= #results.inspect %></p>
</div>
</div>
But that 'inspected' Quiz instance returns 'nil' for all the answers1, answers2 etc attributes. Any idea why that would be? Is there something I'm NOT doing to save the user's answers to the database?
The reason it shows nil is because you are not setting the variable.
After creating and saving, you redirect to results_path and the variable #results does not persist during a redirect. Without seeing the full code, I'll have to guess at your naming conventions but there are two ways to do this.
1) If you want to redirect to the index then in the code for your index action, you can set the variable:
#results = Quiz.last
This is easy to work with in development because you are the only user and this will always return the last quiz you created. Not so great in production.
2) The alternative is to redirect to the show action for that quiz.
def create
#results = Quiz.new(post_params)
if #results.save
redirect_to result_path(#results)
else
# error handle here
end
end
Again, I have had to guess that result_path is the correct path. Without seeing the full routes file, I cannot be sure but you can rename accordingly if necessary.

Rails, STI and 'becomes' - f.object.errors not showing in view

My question is: why doesn't .becomes pass errors over to the new object? Isn't this the expected behaviour?
I have the following single table inheritance classes in a rails app:
class Document < ActiveRecord::Base
validates :title, :presence => true
end
class LegalDocument < Document
end
class MarketingDocument < Document
end
I want to use the same controller and set of views to edit both LegalDocuments and MarketingDocuments, so I am using DocumentsController < ApplicationController with the following edit and update actions:
def edit
#document = Document.find(params[:id])
end
def update
#document = Document.find(params[:id])
if #document.update_attributes(params[:document])
redirect_to documents_path, :notice => "#{t(:Document)} was successfully updated."
else
render :action => "edit"
end
end
and the following in my edit view:
<%= form_for #document.becomes(Document) do |f| %>
<% if f.object.errors.present? %>
<div class="error_message">
<h4><%= pluralize(f.object.errors.count, 'error') %> occurred</h4>
</div>
<% end %>
<div>
<%= f.label :title %>
<%= f.text_field :title, :class => "inputText" %>
</div>
<%= f.submit %>
<% end %>
If title is filled in, the documents update correctly.
If title is left blank, I am returned to the edit view BUT the error is not shown.
From debugging, I know it's not showing because f.object.errors is nil. However, from debugging, I also know #document.errors is NOT nil, as expected.
My question is: why doesn't .becomes pass errors over to the new object? Isn't this the expected behaviour?
Yes, I noticed that too.
Just change f.object.errors.present? by #document.errors.any? ( or #document.errors.present?).
If you really want to use f.object.errors.present?, write becomes in the controller (both edit and update actions) instead of in the view:
def edit
#document = Document.find(params[:id]).becomes(Document)
end
def update
#document = Document.find(params[:id]).becomes(Document)
# ....
end
And then in the view:
<%= form_for #document do |f| %>
<% if f.object.errors.present? %>
<p>Errrorsss....</p>
<% end %>
#.....
It happens because the url of the form is build according to #document.becomes(Document) (=> PUT document/:id) but #document is created according to its "true" class (a subclass of Document).
If you have pry (highly recommended), write:
def update
#document = Document.find(params[:id])
binding.pry
# ...
end
And then inspect #document. You will see that #document is an instance of LegalDocument or the other subclass even though you called #document.becomes(Document) in your form.
So in final f.object and #document are not the same.
This explains why you can't see f.object.errors when validation fails.
Edit
The 'best way' to deal with STI and form is NOT to use becomes:
<= form_for #document, url: { controller: 'documents', action: 'update' }, as: :document do |f| %>
<% if #document.errors.any? %>
# or if f.object.errors.any?
# handle validation errors
<% end %>
# your form...
<% end %>
This enables you:
to have only one controller (documents_controller)
to have only one resource (resources :documents)
it keeps trace of your subclasses: a LegalDocument will be store as a LegalDocument. No conversion: You don't have to store its class before the conversion to Document and then reassign it later.
Plus, your subclass is available in your form, so you can (let's imagine) build a select for the type.
your controller looks cleaner: #document = Document.find params[:id] nothing more. Just like a classic resource.
If you want to share this form across different actions(typically edit and new):
<%= form_for #document, url: { controller: 'media_files', action: action }, as: :media_file do |f| %>%>
# edit.html.erb
<%= render 'form', action: 'update' %>
# new.html.erb
<%= render 'form', action: 'create' %>
Pretty much it is a bug and it should work as you initially expected. The following patch to address the issue looks like it was pulled back in October
https://github.com/lazyatom/rails/commit/73cb0f98289923c8fa0287bf1cc8857664078d43

Resources