Multi-page record creation - ruby-on-rails

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 .

Related

How to make multi-step form in Rails and prompt user to sign-up after filling form?

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.

flexible system to destroy each records in batch

client_skipped_day_controller.rb
class ClientSkippedDaysController < ApplicationController
before_action :check_client_on_exist, only: [:create]
def index
#client_skipped_days = ClientSkippedDay.order_by(params[:sort_by], params[:direction])
if params[:date].present?
#client_skipped_days = #client_skipped_days.where('skipped_at = ?', Date.parse(params[:date]))
end
render json: #client_skipped_days, status: :ok
end
def create
#client_skipped_days = ClientSkippedDay.create!(client_skipped_days_params)
render json: #client_skipped_days, status: :created
end
def destroy
end
private
def client_skipped_days_params
params.permit(client_skipped_days: %i[client_id skipped_at])[:client_skipped_days]
end
def check_client_on_exist
client_skipped_days_params.each do |day|
ClientSkippedDay.find_by(day)&.destroy
end
end
end
My code works if I try to delete only one record, like a :
Parameters: {"client_skipped_days"=>[{"client_id"=>533, "skipped_at"=>"2019-02-24"}], "client_skipped_day"=>{}}
But if I try to delete each hash in the array, it's didn't work :(
Parameters: {"client_skipped_days"=>[{"client_id"=>533, "skipped_at"=>"2019-02-24"}, {"client_id"=>512, "skipped_at"=>"2019-02-24"}], "client_skipped_day"=>{}}
Only one record will be deleted, but how to add the ability to delete all records? which coincide with the parameters that come from the controller?
And it must be a flexible system to remove if 1 hash in the array and immediately a collection of hashes in the array. Tell me how to do it.
Instead of looping over the params and finding each record one by one you could also consider using multiple #where queries combining them together with the use of #or and loop over the resulting records.
def client_skipped_days_params
params.permit(client_skipped_days: [:client_id, :skipped_at])
# removed `.values` ^
end
def check_client_on_exist
destroyed_records, undestroyed_records =
client_skipped_days_params
.fetch(:client_skipped_days, []) # get the array or use an empty array as default
.map(&ClientSkippedDay.method(:where)) # build individual queries
.reduce(ClientSkippedDay.none, :or) # stitch the queries together using #or
.partition(&:destroy) # call #destroy on each item in the collection, separating destroyed once from undestroyed once
end
In the above example the resulting destroyed records are present in the destroyed_records variable and the records that could not be destroyed are present in the undestroyed_records variable. If you don't care about the result you can leave this out. If you want to raise an exception if a record cannot be destroyed use #destroy! instead (call upon each collection item).
Alternatively you can destroy all records by calling #destroy_all (called upon the collection), but it will simply return an array of records without differentiating the destroyed records from the undestroyed records. This method will still instantiate the records and destroy them one by one with the advantage that callbacks are still triggered.
The faster option is calling #delete_all (called upon the collection). This will destroy all records with one single query. However records are not instantiated when destroyed, meaning that callbacks will not be triggered.
def check_client_on_exist
destroyed_record_count =
# ...
.reduce(ClientSkippedDay.none, :or)
.delete_all # delete all records with a single query (without instantiation)
end
references:
ActionController::Parameters#fetch
Array#map
ActiveRecord::QueryMethods#none
Enumerable#reduce
Enumerable#partition
You need to loop over your array instead of just taking the first value out of it. I don’t understand the params that you have, so I’m assuming that you want to do your find_by using the Hash of client_id and skipped_at.
Also, Ruby 2.3.0 introduced the safe navigation operator, which is what that &. is if you aren’t used to it. http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/
Since find_by either returns an ActiveRecord object or nil, it’s a great time to use the safe navigation operator to shorten things up.
def client_skipped_days_params
params.permit(client_skipped_days: %i[client_id skipped_at])[:client_skipped_days]
end
def check_client_on_exist
client_skipped_days_params.each do |day|
ClientSkippedDay.find_by(day)&.destroy
end
end
Note, I’m not sure what your client_skipped_day Hash is. I assumed you’re making it possible to delete a single day, or delete in bulk. I would warn against having it do two things. Just make the client always send an array for this action and things will be easier for you. If you can do that, then you can make client_skipped_days required.
def client_skipped_days_params
params.require(:client_skipped_days).permit(%i[client_id skipped_at])
end
This will raise a 422 error to the client if they don’t provide the client_skipped_days key.
If this isn’t possible, then you’ll need to add an if to check_on_exist to make sure that client_skipped_days_params is not null (because they’re using client_skipped_day).

saving multiple tables at once in rails

I have an action that update three tables at once like this:
def action_save
#user.update(param_param_list1)
#application.update(param_list2)
#college.update(param_list3)
end
but to make the program better, I want to either save all three together at once or not at all
Use an ActiveRecord::Transaction:
def action_save
#college.transaction do
#user.update!(param_param_list1)
#application.update!(param_list2)
#college.update!(param_list3)
end
end
A transaction ensures that all the database action within that block are performed. Or if there is an error, then the whole transaction is rolled back.

How do I respect RESTful methods when using find_or_initialize_by in Rails 3.2?

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.

RoR ActiveRecord save method

Im new to Rails, im trying to execute the save method within an ActionController's Create method multiple times to insert multiple values
def create
#pin = Pin.new(params[:pin])
i = 1
while i < 10
if #pin.save
end
end
redirect_to #pin
end
This works but only inserts one record
there's no Contraints that enforces uniqueness of Records in my Database.
Please how do i correct this?
One AR objects maps to one row. You need to create new object for each row you want added.
Something like that:
10.times do
pin = Pin.new(params[:pin])
pin.save
end
or
10.times do
Pin.create(params[:pin]
end
create method creates an AR object and saves it in the database.
However, you cannot redirect to 10 objects.
you should extend your resource with create_multiple method and send params as array, see the details here

Resources