I have a rails app that imports all your Facebook contacts. This takes some time. I would like to be able to show a "please wait" page while the importing keeps happening in the back.
It seems that I cannot put render and redirect_to on the same action in the controller. How can I do this?
if #not_first_time
Authentication.delay.update_contact_list(current_user)
else
render 'some page telling the user to wait'
Authentication.import_contact_list(current_user)
end
redirect_to :root_path, :notice => 'Succesfully logged in'
If it is the users first time in the site, i want to render a "please wait page", start importing, and once its done redirect to the root path, where a lot of processing with this data happens
If it is not the first time, then put the contact update in the background (using the delayed_jobs gem) and go straight to the home page
I'm using the fb_graph gem to import the contacts. Here's the method
def self.import_contact_list(user)
user.facebook.friends.each do |contact|
contact_hash = { 'provider' => 'facebook', 'uid' => contact.identifier, 'name' => contact.name, 'image' => contact.picture(size='large') }
unless new_contact = Authentication.find_from_hash(contact_hash)
##create the new contact
new_contact = Authentication.create_contact_from_hash(contact_hash)
end
unless relationship = Relationship.find_from_hash(user, new_contact)
#create the relationship if it is inexistent
relationship = Relationship.create_from_hash(user, new_contact)
end
end
end
Edit
I added the solution suggested below, it works!
Here's is my 'wait while we import contacts' view from the action "wait"
<script>
jQuery(document).ready(function() {
$.get( "/import_contacts", function(data) {
window.location.replace("/")
});
});
</script>
<% title 'importing your contacts' %>
<h1>Please wait while we import your contacts</h1>
<%= image_tag('images/saving.gif') %>
Thanks!
A single request receives a single response - you can't render content and redirect.
If I were you I would always do the lengthy process in delayed job - tying up passenger/unicorn instances is never a great idea. Render a 'please wait page' that refreshes periodically to check whether the delayed job has completed (if you stash the id of the delayed job you can test whether it is still in the db. When te job completes it will be deleted). When the job is complete, redirect to the results page. You could also do the periodic checking bit via ajax.
Related
Something very funny is happening... my controller has two checks that are made separately before it allows the form to be submitted (the code below is simplistic, there are other things happening). If either checkpoint fails, the new page is re-rendered.
Here's the code:
def create
if checkpointone == fail
render 'new'
else
if checkpointtwo == fail
render 'new'
else
redirect_to action: 'success'
end
end
end
Here's the funny flow problem I'm running into:
user enters data that fails checkpointone and passes checkpointtwo
user submits
new page successfully re-rendered with original parameters and error message
user still enters incorrect data (either checkpointone fails again, or it passes but checkpointtwo fails)
user submits
application fails with an error No route matches [PATCH] "/requests"
But why on earth is it looking for a PATCH all of the sudden? Who told it to? The weird thing is that if I start with a fail in checkpointtwo and a pass in checkpointone, from then on, I can make any number of fails and resubmissions in various combinations, and I'll always get the correct action: new page re-rendered with original parameters and error message.
Snippet of view code:
<%= form_for #requestrecord, :url => { action: 'create' }, :html=> {:id => 'form'} do |f| %>
Snippet of routes code:
get 'requests/new', to: 'requests#new', as: 'new_request'
post 'requests', to: 'requests#create'
In Rails 4, PATCH is used for updates. Could it be that your record is being created even when a checkpoint fails. This would cause Rails to think that the subsequent request is an update request, rather than create request.
I have a page that lets one create records - if the validations aren't satisfied, it redirects to the same page and shows an error message. Here's that snip from the controller:
def create
#signature = Signature.new(signature_params)
if #signature.save
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
end
The trouble is, this is resulting in a full page refresh - so the error message isn't visible because the input form is placed under the fold of the page. I can place it at the top of the page, of course, but is there a way to show the message without reloading the page? Thanks.
OK, so here's what I've settled on:
1) I'm handling validation on the client side with HTML5 "required" attributes - they were created for this explicit purpose and no other gems or plugins are needed. They are supported in all major browsers. Details in this article.
2) I've moved the error messages to the top of the page to handle the case in which a user either is on an old or mobile browser or has JavaScript disabled. Error messages must work with a complete request-response cycle (even if this means re-loading the page) before they work with anything else - this is the unobtrusive JavaScript approach.
3) For the AJAX version, I'm going to be using remote: => true on the form element as explained in the Rails guides. I might be making this open source once I'm done with the callback part of it, and will post a link here.
Obviously, handling errors with flash is the most uniform & DRY way to show the user what's going on, but if you're willing to think outside the box, you'll be able to use Ajax to accomplish a similar job by just handling the errors yourself:
Code Example
#app/controllers/signatures_controller.rb
def create
#signature = Signature.new(signature_params)
if #signature.save
#success = "true"
end
respond_to do |format|
format.js { #errors = error_messages(#signature) }
format.html {
if #success.defined?
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
}
end
end
#app/views/signatures/create.js.erb
<% unless #success.defined? %>
alert(<%=j #errors.inspect() %>)
<% end %>
#app/assets/javascripts/signatures.js
$(document).on("submit", "#signature_form", function() {
$.ajax({
url: "/signatures"
type: "POST"
data: $(this).parent().serialize(); //serialize the form (not the button)
error: function() {
alert("Sorry, there was an error!");
}
});
});
You'd actually be better using JSON for this. If you like the idea, I can refactor it to include JSON for you!
i wanted to perform an action mailer method after an ajax method completes. im building a twitter app essentially, and wanted an email notification to be sent after someone clicks 'follow', which is done asynchronously.
i gave the follow button an id
<%= f.submit "Follow", :class => "btn btn-large btn-primary",
:id => "follow_button"%>
and then used jquery
$("#follow_button").bind('ajax:success', function() {
});
however, im really sure how i can reference my UserMailer in jquery. ultimately im trying to perform this line after my ajax is complete
UserMailer.is_now_following(#user, current_user).deliver
thanks!
hmmm i tried adding that line of code in my create function (to create the relationship of the follow) but it lags my ajax quite a lot. the ajax is to render the 'unfollow' button after the 'follow' is clicked btw.
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html {redirect_to #user}
format.js
end
#UserMailer.is_now_following(#user, current_user).deliver
end
i commented it out. is this what you meant for adding it after my ajax call is successful?
also, how do you put a job on queue? thanks!
The better solution is doing that only in your server not in your client side.
If you do like you want you need doing 2 requests. 1 to follow people and 1 to launch mail. If you user stop this application between this 2 requests, no email is send.
The better solution is to launch your Mailer directly in your follow action. In your controller, you know if the request is a success or not. If the request is a success launch the email.
If you want more reactivity and avoid doing this job directly in your action, you can push the mailer action to a job queue.
Please excuse the simplicity and length of this, but I have a little test app that has a users table, with name, email, and salary attributes. I created a mailer that will send out a report of this data to a specific user at my discretion, in other words, when I click a button. My button is created using link_to, and calls an action in my main users_controller, which then calls the mailer action. (I hope that just made sense). It looks like the following and is working just as I hoped; I'm just looking to know if this is the right way to do something like this:
In my users_controller (created by the scaffold generator):
def sendemail
#user = User.find(params[:id])
UserMailer.welcome_email(#user).deliver
redirect_to user_path(#user)
flash[:notice] = 'Email has been sent!'
end
In the user_mailer.rb file:
def welcome_email(user)
#user = user
#url = "http://example.com/login"
mail(:to => user.email,
:subject => "Welcome to My Awesome Site")
end
In the User's show.html.erb page, this is how the email gets sent:
<%= link_to "Send Email", sendemail_user_path(#user) %>
In my routes.rb file, so that everything executes properly (which it does):
resources :users do
member do
get 'sendemail'
end
So, having said all this, it works just like it should. I click on the user's show.html.erb page where I would have the data and charts that I want to eventually display, and at my discretion, I can kick out an email to that user with this data, or whatever I put in the mailer.html.erb file. When it is sent, it flashes the message I specified in the controller, and leaves me on that page, just as I specified; so it's working. I just want to know, is this the right and most ruby/railsy way to do things?
This code looks very similar to the Rails Guides Action Mailer example, so suffice it to say you are creating railsy code.
Also, if your application evolved into a much grander scale you would want to consider delivering emails via a background job to avoid email deliveries from blocking the current thread.
Otherwise the code looks great. Of course, you would most likely send an email after doing something successful in a controller, rather than have a dedicated action for emailing directly. For instance, you might have a welcome action that sends an email on a successful user record save.
Think about a simple Rails scaffold application with a "new" action containing a form to add records to a database with a "save" button. After the "create" action the controller redirects to the "show" action, where the user can use the "edit" link to edit the just inserted record. So far, so simple.
But if the user instead uses the browser's back button after creating a record to get back to the "new" action, the browser shows the form with the values the user just has entered. Now he changes some values and presses "save" again. He thinks that this would change the record, but of course this creates a new record.
What is the preferred way to prevent such duplicate entries? I'm looking for a general solution, maybe based on cookies or JavaScript.
After some investigations I found a suitable solution based on cookies. Here it is:
In the controller's "new" action, a timestamp with the current time is generated and rendered in the form as hidden field. When the user submits the form, this timestamps gets back to the controller's "create" action. After creating the record, this timestamp is stored in the session cookie. If the user goes back to the "new" form via browser's back button, he gets a stale form, which means its timestamp is older than the one stored in the cookie. This is checked before creating the record and results in an error message.
Here is the controller code:
def new
#post = Post.new
#stale_form_check_timestamp = Time.now.to_i
end
def create
#post = Post.new(params[:post])
if session[:last_created_at].to_i > params[:timestamp].to_i
flash[:error] = 'This form is stale!'
render 'new'
else
#post.save!
#stale_form_check_timestamp = Time.now.to_i
session[:last_created_at] = #stale_form_check_timestamp
end
end
And here the form code:
- form_for #post do |f|
= tag :input, :type => 'hidden', :name => 'timestamp', :value => #stale_form_check_timestamp
= f.input :some_field
= .......
When I had that same problem I created this little gem that solves it. When the user hits back, he's redirected to the edit_path of the record, instead of going back to the new_path.
https://github.com/yossi-shasho/redirect_on_back
You can do something like:
def create
#user = User.new(params[:user])
if result = #user.save
redirect_on_back_to edit_user_path(#user) # If user hits 'back' he'll be redirected to edit_user_path
redirect_to #user
end
end
Your model validations will ensure things like email addresses are unique, but I think this is more about usability and experience than anything else.
Say you are talking about an account creation form. First of all, your form submit button should say something like "Create Account", instead of just "Submit". Then depending on whether it was successful or not, show a message like either "Account successfully created" or "There were errors creating your account". If the user sees this message, they will know what happened.
Sure you can't prevent someone from hitting the back button and hitting enter again, but you should design for the majority of use cases. If they happen to hit back, they will see the button that says "Create Account". You should probably have some other text on the page that says "Please sign up for a new account to get started".
Just my $0.02.
Session or cookie may result in sides effects.
I totally agree : if there is a way to validate with your model, it's the safest way to prevent duplicate records.
Still you can do 2 things. Prevent browser caching : fields will appear empty in the form when the user clicks on the back button. And disable the "Create" button when clicked.
= f.submit "Create", :disable_with => "Processing..."
When your user will press the back button the button will be disabled.
You can use validators to make sure that no duplicate values are inserted. In this case validates_uniqueness_of :field
If you for example want to prevent users from having the same email address you could put the following code in your user model.
validates_uniqueness_of :email
This checks the column for any previous entries that are the same as the one your trying to inert.
Good luck
base on #Georg Ledermann answer i make this little snip of code for redirect to edit path if the user hits back and then hits create.
#objects_controller.rb
def new
#object = Object.new
#stale_form_check = Time.now.to_i
end
def create
#object = Object.new(object_params)
#function defined in application_controller.rb
redirect_to_on_back_and_create(#object)
end
#application_controller.rb
private
def redirect_to_on_back_and_create(object)
if session[:last_stale].present? and session[:last_stale_id].present? and session[:last_stale].to_i == params[:stale_form_check].to_i
redirect_to edit_polymorphic_path(object.class.find(session[:last_stale_id].to_i)), alert: "Este #{object.model_name.human} ya ha sido creado, puedes editarlo a continuación"
else
if object.save
session[:last_stale] = params[:stale_form_check].to_i
session[:last_stale_id] = object.id
redirect_to object, notice: "#{object.model_name.human} Creado con éxito"
else
render :new
end
end
end
And finally add the #stale_form_check param to your form
<%= hidden_field_tag :stale_form_check, #stale_form_check %>
You could always abstracts this method where you need it, but in this way you could avoid lots of repetition in your project if you need this behavior in many parts
Hope it helps the next one, i used to use redirect_on_back gem, but it didn't work for me this time, the _usec param that this gem uses, was always been reset, so it can't compare in every time when it was need
Here's something that worked for me.
You will need to do 2 things: Create a method in your controller and add a conditional statement in that same controller under your 'create' method.
1) Your method should return the total count of that object from that user.
EX:
def user
current_user.object.count
end
2) Add conditional statement in your 'create' method.
EXAMPLE:
def create
#object = Object.create(object_params)
#object.save if user == 0
redirect_to x_path
end
I hope this helps!
Add html: { autocomplete: "off" } in your form_for like this:
<%= form_for #object, url: xxx_path, html: { autocomplete: "off" } do |f| %>