I'm working on a project in which I have a form, a part of this form is a button which authenticates with a 3rd party service, they click the button, are redirected to the authentication page, then redirected back to the form. When they return I'd like the form to be in the same state it was when they left the page, but I'm not sure of a good way to do this. It's a large form so users would be annoyed if they had to fill it all out again.
Any help with this would be greatly appreciated.
By default you define new method as an instance of object without any data:
def new
#post = Post.new
end
However, it is possible to instantiate object with default values:
def new_with_data
#post = Post.new(title: 'Some default title')
end
Therefore, when you want to store state of form and display it on next page, without storing it in database, you can store it in session / cookie / params (e.g. GET, if they are not sensitive or POST, if you just want to show it on next page; carry data to next view).
def confirm_form_data
#post = Post.new(params[:post])
end
Of course, you don't have to use any models here:
def confirm_data
#data = params[:post]
# use #data in your view
end
Related
I have model in my rails App called "Request". It consist around of the 12 fields. It has relations with user model like User has_many: request, Request belongs_to: User. For users i use Devise. My problem is next. I want that user from home page of my app has a possibility to start creating new request. It should be multi-step form like 3 steps with 4 inputs in each step. After filling all step i need to prompt user to sign up. I don't want that new row in Request table would be created until user would do Sign up. So the process should looks like user goes throw each step, we store all input data by someway, after user do sign-up we create new user in Users table and new request in Requests table. I don't want to store request until user would be registered because in this case i would have in my db a lot of requests that don't belong to any user. So how could i achieve my aim?
UPDATE:
So with users suggests and with searching i get some progress. So now i have form on one page (all 12 fields), after click on submit it goes to "create" action in RequestController where i do such code:
if current_user.nil?
session[:request] = params
# Redirect the user to register/login
redirect_to new_user_registration_path
So i get all params, store them in session[:request] and redirect to Sign-up page. Than in ApplicationController i have added code:
if session[:request].present?
# save list
#request = current_user.requests.create(session[:request]["request"])
# clear session
session[:request] = nil
which connect request parameters with current_user and create new record in db.
So now only one problem, how to make multistep for my form, which in final result would be sent to create action?
I think you can use the wicked gem for step by step wizard.
And in the first step html template keep only the input fields which you require , and in second step store the first step values in hidden field, similarly in third step you can store first and second step values in hidden field.
and in controller Just assign the values in #request variable
steps :step_one, :step_two, :step_three
def request_steps
#request = Request.new
#request.assign_attributes(params[:request])
case step
when :step_one
#add code here you want to do in step one
when :step_two
#add code here you want to do in step two
when :step_three
#add code here you want to do in step three
end
render_wizard
end
Alternate solution:
If you don't want to store in hidden field and not using wicked and have multiple steps actions then another solution is to store the request inputs in session like below:
def step1
session[:user_request] ||= {}
session[:user_request].merge!{params[request]} #merge input from step1
# render to show step2
end
def step2
session[:user_request] ||= {}
session[:user_request].merge!{params[request]} #merge input from step2
# render to show step3
end
def step3
session[:user_request] ||= {}
session[:user_request].merge!{params[request]} #merge input from step3
# render to login/signup_page
end
and after user login you can find the request data using session[:user_request] and save the request with logged in user and clear session[:user_request] like in your devise session controller
after_filter :after_login, :only => :create
def after_login
if session[:user_request]
request = current_user.requests.build(session[:user_request])
session[:user_request] = nil if request.save
end
end
Hope it would help you.
A suggestion would be to store the values on Front-end, and in the last step send all the informations. This can be achieved using javascrit, and all the variables would be naturally stored in variables. I think this is one of the the easiest ways.
Another idea is to create a few methods in controller (and routes) that gathers all the informations, it can be like (using preferable ajax, or normal post requests):
def step1
params = params[:params]
#...
end
def step2
params = params[:params]
#...
end
And then you can create a column that temporarily stores all the informations (in just on column, in format json or hash), and it just send to the right columns after the user confirmed all the infos. You can then create a cron job to delete records from temporaries columns.
These are just a few suggestions. I'm looking foward to see more ideas.
I have an ActiveRecord model named Document and have implemented CRUD operations around it. I just have a problem with persisting a Document instance between requests when validation fails (be cause I wanna redirect to another page when this happens).
First, I tried storing the instance in the flash session:
# documents_controller.rb
def new
#document = flash[:document] || Document.new
end
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
With the code above, I was expecting that the actual Document instance was stored in the flash session, but instead it became a string which looks somewhat like #<Document:0xad32368>. After searching online for a while, I found out that for some reasons you cannot store ActiveRecord objects in sessions.
There are a lot of suggestions about just storing the object's id in the flash session, but I can't do that because as you can see, the object is not yet stored in the database.
Next, I tried reconstructing the Document instance after the redirect, taking advantage of the instance's attributes method (which returns a serializeable hash that can be stored in the session):
# documents_controller.rb
def new
#document = Document.new(flash[:document_hash] || {})
end
def create
...
flash[:document_attributes] = document.attributes
redirect_to new_document_path
end
This almost solved the problem, except for the part in which the validation errors (document.errors) are not preserved. Also, if this is used to persist an instance already stored in the database (in the case of failed validations when updating a Document instance), I'm not sure which between the original attributes and the new attributes will get persisted.
Right now I've already run out ideas to try. Anyone who has a decent solution for this?
EDIT:
You might be wondering why I still have to redirect to another page instead of just rendering the new document view template or the new action in the create method. I did so because there are some things in my views that are dependent on the current controller method. For example, I have a tab which needs to be highlighted when you are on the document creation page (done by checking if action_name == "new" and controller_name == "documents"). If I do:
def create
...
render action: "new"
end
the tab will not get highlighted because action_name will now be create. I also can't just add additional condition to highlight the tab if action_name == "create" because documents can also be created from the the index page (documents_path). Documents can also be updated from the index page (documents_path) or from the detail page (document_path(document)), and if validation fails in the update method, I'd like to redirect to the previous page.
If I really need to fake persisting something between requests (all of the variables that you set are lost between requests), I will ususally put the relevant attributes into hidden fields in the new form.
In your case, this is overkill. In your code, you are redirecting, which causes a new request:
def create
document = Document.new(document_params)
if document.save
return redirect_to documents_path
end
flash[:document] = document
redirect_to new_document_path
end
You can easily render the output of another action, instead of redirecting, by using render action: 'action_to_render'. So in your example, this would probably be:
def create
#document = Document.new(document_params)
if #document.save
render action: 'index'
else
render action: 'new'
end
end
Which can be simplified to:
def create
#document = Document.new(document_params)
action_to_render = #document.save ? 'index' : 'new'
render action_to_render
end
If you need extra logic from the action, you can refactor the logic to a method called from both actions, or simply call the other action from the current one.
It is fine once in a while, but I would caution that having to jerk around with the rendering too much is usually indicative of poor architecture.
Edit:
An additional option, given the newly highlighted constraints, could be to make the new and create methods the same. Remove the new action and routes, and make create answer for GET and PATCH requests. The action might look something like:
def create
#document = Document.new(document_params)
request.patch? && #document.save && redirect_to( documents_path )
end
I actually use something very similar to this for almost all of my controllers, as it tends to DRY things significantly (as you can remove the extra probably identical view, as well)
Another option would be to just use an instance variable to keep track of the active tab in this instance, and make the rest of the code a lot cleaner.
SOLVED
I was able to make a workaround for it using ActiveSupport::Cache::Store (as suggested by #AntiFun). First I created a fake_flash method which acts closely like the flash sessions except that it uses the cache to store the data, and it looks like this:
def fake_flash(key, value)
if value
Rails.cache.write key, value
else
object = Rails.cache.read key
Rails.cache.delete key
object
end
end
And then I just used it like the flash session.
# documents_controller.rb
def new
...
#document = fake_flash[:document] || Document.new
...
end
def create
document = Document.new document_params
...
# if validation fails
fake_flash :document, document
redirect_to new_document_page
end
I have a resource in my project that collects some information from a user. Basically it's a form that they fill out before they can access another area of the site. It then sets a cookie for a week, but if they come back it will look up their previous entry and keep their preferences tied to them (and will update any details as long as the email address matches).
Currently I have a Applicants controller that looks like this:
class ApplicantsController < ApplicationController
...
def create
#applicant = Applicant.find_or_initialize_by_email(params[:applicant])
if #applicant.new_record? ? #applicant.save : #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
def update
if #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
end
The set_cookie_and_redirect is a private method that just sets some cookies and redirects the user to a page. The code works, but it just feels dirty. It's essentially updating a record within the create method under the condition that it's not a new record. I'm also forced to have an update method in case an existing record comes back with a validation error--the form helper will then switch the form over to sending to the update method.
So to my point... is there a more appropriate way to push the update_attributes call in the create method to the update method? Or better put, is there a better way to respect the RESTful methods in isolating the create and update functionality?
UPDATE: I wanted to be a little more specific too. If the user has filled this form out before it will set a cookie so they don't have to fill it out again for seven days. However after seven days the cookie is expired and they see the form again. The controller doesn't know if the user is new or existing until they add user input into the form which is then compared based on the email address.
Thanks in advance! I definitely look forward to anyone's thoughts on this.
The create method should only create, and the update method should only update. Let Rails decide which is going to happen based on what is inside of #applicant when the form is rendered - It essentially does what you're doing: Checks if the record is new or not, and sends it to update/create accordingly. Example:
def applicant
#applicant = Applicant.find_or_initialize_by_email(cookies[:email])
# renders applicant.html.erb form
end
<%= form_for #applicant do |f| %>
# ... fields ...
<% end %>
def create
#applicant = Applicant.new(params[:applicant])
#applicant.save
# .. etc.
end
def update
#applicant = Applicant.find_by_email(cookies[:email])
#applicant.update_attributes(params[:applicant])
# ... etc.
end
Rails will send the request to the correct action based on the new_record? status of the Applicant object.
I have a form where the user signs up and creates an Account, an User and a Website.
def new
#account = Account.new
#account.users.build
#account.websites.build
...
end
def create
#account = Account.new(params[:account])
...
Everything works fine. Now, I want to create a default Page with Page.title = "homepage" and Page.body = "".
How can I do that? I tried different options and it doesn't work. For example, I do this #account.websites.pages.build and I get this undefined method pages for []:ActiveRecord::Relation.
The collection returned by #account.websites is an array, rails can't intuit which member of the collection you're trying to create an associated object on... You need to specify which website you want to build a page for, ie
#account.websites.first.pages.build
puts 'newbie question'
I have an account sign-up that spans multiple pages, but I'm not exactly wrapping my head around creating a new instance that is tied to a database but only adds to the database if all pages are completed.
I have three actions:
def index
#ticket = Ticket.new
end
def signup_a
end
def signup_b
end
The index page only collects a single text field, then passes that to populate the field in signup_a, then to b, which is where the record is finally added to the database. How do I go from passing the variable from index to A to B in a Ticket object without actually adding it to the DB?
Edit---
I think I got tripped up that the line
if #order.save
actually saves the object...I thought it just performed a check.
You can keep it in the session and save it in the database once all the steps are complete .