Rails 6 create order confirmation before submit
tl/dr: You need to add parameters to your create request.
Why this is happening
The /confirm
view is being rendered with an (unsaved) @cash_transaction
object, however that object is not being used and so the information is being lost when the page is rendered.
The line:
<%= link_to 'Confim', cash_transactions_withdrawals_path(@cash_transaction), method: :post %>
Will submit a POST
request with no parameters to the /cash_transactions/withdrawals#create
(because you've given it no parameters to post). It doesn't know to include the params from the previous request.
There are a few options to fix this... you can add params as URL parameters in link_to like this, however I wouldn't recommend posting with params in the URL.
You can use button_to
instead, and pass in the cash_transaction
arguments from the previous request in the params:
option (or pull them out of the unsaved @cash_transaction object).
Approach #1 - reuse create params
# Get them from the params sent in the previous request. In the controller...
def create
@cash_transaction = CashTransaction.create!(cash_transaction_params)
# etc...
end
#...
protected
def cash_transaction_params
params[:cash_transaction].permit(:amount, :whatever)
end
helper_method :cash_transaction_params
# In the view
<%= button_to 'Confirm', {action: 'create', params: cash_transaction_params}
Approach #2 - Access attributes from the model you built
<%= button_to 'Confirm', {action: 'create', params: @cash_transaction.attributes.slice('amount', 'other_attribute') }
Or you could do something like render the form again but hidden and have the "confirm" button submit the hidden form (with { action: :create }
instead of { action: :confirm}
). This last solution is probably the easiest to understand.
Order confirmation page in rails
There were a few answers on this
question that got me halfway there,
but the problem was that I wasn't
quite sure how to set up the form in
the rails view so that it would take
the user to a confirmation page with
all their details instead of a create
action.
Directing a form to a non standard page is pretty simple.
Add a url option form_for.
Such that
<% form_for :order do |f| %>
becomes
<% form_for :order :url => {:action => "confirm"} do |f| %>
You'll need to crate the confirm action in your routes, but that only involves this:
map.resources :orders, :collection => {:confirm => :get}
All you need now is a basic controller action and a view:
def confirm
@order = Order.new(params[:order])
unless @order.valid?
render :action => :new
else
end
end
Your view should look almost identical to the show view, with the addition of a form submitting @order to the create action.
How to show the content of the form just entered in the :confirm = option on Ruby on Rails
Given that your loading attachments it may not be a bad idea to render a staging view including information derived from the attachment allowing the user to confirm. As in display the file if it's an image, or the first paragraph of text if it's a text file, etc.
It's going to take more work than the just adding a confirm pop up, but I feel the enhanced user experience is worth the extra effort.
I'm not familiar with the way that paperclip works. So you're mostly on your own for the intimate details.
You will probably have to create a record before the staging view can be rendered with the sample of the uploaded file. To accomplish that I'd set up an "active" column on the model in question, that defaults to false.
Usage would look something like this:
- User complets new form.
- Attachment is updated and records are saved, with the active field set to false.
- Redirected to confirmation page that is essentially the show page with a confirm link/button and a cancel link/button
a. When the confirm link/button is clicked it sends a request to the controller triggering the update action on this record setting active to true.
b. When the cancel link/button is clicked it sends a request to the controller trigering the destroy action on this record.
All that's left is to set up a recurring task to remove objects that are inactive and were crated long enough ago that it's safe to assume the user has just ended the browser session.
Is there a standard way to get an ActiveRecord item within Ruby on Rails from a given URL?
On your basic example something like this should work.
def get_object_from_url url_string
class_string = url_string.split('/')[-2]
object_id = url_string.split('/')[-1]
class_string.camelize.constantize.find_by_id(object_id)
end
Would require more complicated logic depending on the url's.
How to send confirmation request from one model to another
I would add a Boolean "confirmed" field to the Job model with "false" as a default value.
When the child completes the job, the record is saved in the database with the value confirmed: false.
Then you can arrange your controllers so that the User can see the jobs which are pending (confirmed: false, i.e created but not confirmed) and have the possibility to confirm the job creation or to refuse it (destroy).
pending_jobs = Jobs.where(confirmed :false)
Then you can list the jobs which are confirmed and display them the way you want.
jobs = Job.where(confirmed: true)
Its a bit of a workaround but makes it easy to handle and display jobs records.
How to redirect page after confirmation in Devise
I figured it out. I'm not sure if this changes with the update Devise did yesterday in making Devise::Mailer put most of its functionality into a module. (See the code and ticket for more information).
Basically it boils down to not being able to access the session
inside of a mailer view. Therefore you have to pass the redirect as a variable. Devise uses an after_create
method on your resource (User
in my case) which then sends the confirmation email. This meant I couldn't just pass the session variable directly to the mailer. Thus I feel like this is a pretty nasty work-around in order to get this functionality, but here is the code:
To get the redirect_to
variable into the mailer you have to add a variable to the user, thus:
class User < ActiveRecord::Base
…
attr_accessor :return_to
…
end
Then you have to set that variable when you create the user for the first time.
I already had a custom controller setup for registration. (See Devise' Readme on how to set this up, or see @ramc's answer for direction). But it was relatively easy to do this part, I just added it to the parameters and let the rest take care of itself.
class RegistrationsController < Devise::RegistrationsController
def create
params[:user][:return_to] = session[:user_return_to] if session[:user_return_to]
…
super
end
end
Now the user has a variable return_to
which is set. We just need to access that in the confirmation_instructions
email. I've already rewritten part of confirmation_instructions.html.erb so inside there I just added:
<% if @resource.return_to %>
<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token, :redirect_to => @resource.return_to) %>
<% else %>
<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>
<% end %>
(For those who are new to this, @resource
is the variable Devise uses to define your user).
Now once the user clicks on that link we need to redirect them. @ramc's before
filter works well for this:
class ConfirmationsController < Devise::ConfirmationsController
before_filter :set_redirect_location, :only => :show
def set_redirect_location
session[:user_return_to] = params[:redirect_to] if params[:redirect_to]
end
end
That will take care of the case where a new user goes to a protected page then signs up, clicks on the confirmation link and is properly redirected to the protected page.
Now we just need to take care of the case where a user does the above, but instead of clicking on the link, they try to go back to the protected page. In this case they are asked to sign-up/sign-in. They sign-in and then are asked to confirm their email and are given the option of resending the confirmation email. They put in their email and now we need to put the redirect_to
variable in that new confirmation email.
To do this we need to modify the ConfirmationController, similarly to how we did the RegistrationController. This time we need to modify the create
method. The way it works out of the box is to call a class method on the user called send_confirmation_instructions
. We want to rewrite that method so we can pass the return_to
variable into it.
class ConfirmationsController < Devise::ConfirmationsController
def create
self.resource = resource_class.send_confirmation_instructions(params[resource_name],session[:user_return_to])
if resource.errors.empty?
set_flash_message(:notice, :send_instructions) if is_navigational_format?
respond_with resource, :location => after_resending_confirmation_instructions_path_for(resource_name)
else
respond_with_navigational(resource){ render_with_scope :new }
end
end
end
The only thing different than what comes with Devise is that first line of create
, we pass two variables in. Now we need to rewrite that method:
class User < ActiveRecord::Base
def self.send_confirmation_instructions(attributes={},redirect=nil)
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
confirmable.return_to = redirect if confirmable.persisted?
confirmable.resend_confirmation_token if confirmable.persisted?
confirmable
end
end
confirmable
becomes an instance of User (the current user based on email). So we just need to set return_to
.
That's it.
Confirmation message - Javascript rails application
Since you already have a create.js.erb
file, you can use JS to display an alert message or insert a confirmation message into the page like so,
# app/views/.../create.js.erb
$('#new_proposition').before('<p>Proposition correctly submitted!</p>');
$('#new_proposition').fadeOut(1000);
This inserts the <p>Proposition correctly submitted!</p>
before the #new_proposition element. You may want to insert it somewhere else since this element will disappear.
Related Topics
How to Redefine Fixnum's + (Plus) Method in Ruby and Keep Original + Functionality
How to Stringize/Serialize Ruby Code
How to Execute Windows Cli Commands in Ruby
Missing File in Gem After Build
How to Decode Q-Encoded Strings in Ruby
Keyword Arguments Unpacking (Splat) in Ruby
How to Rename or Move Rails's Readme_For_App
New to Ruby - While Loop Issues in Irb
Getting Headers from a Ruby Net::Http Request Before Making the Request
How to Summarize Array of Integers as an Array of Ranges
How to Correctly Install Rvm in Docker
Cocoon Add Association, How to Limit Number of Associations
How to Print Stdout Immediately
How to Upload a File with Watir and Ie