I have a page on my rails application where a user can see and change his information.
For UX reasons, the page is divided into several forms. Each form is used to update a particular set of details, for instance there is a form for general details (name, email address), one for password changes etc.
At the moment, I have only one form, but I'd like to add a few others. What is the best way to handle this? I have two main guess but can't figure out which one would suit best.
1 - Associate an "id" to each form and add some if else statements inside my "update" action in my user controller.
2 - Create new functions (update_password, update_general_info) within my controller and have a go with the routes.
What would be the best solution and a fashionable way to design it?
Cheers
One way to do this is to give a name and value to the submit buttons for each of the forms.
<%= f.submit 'Update Password' %>
Which will render the following HTML:
<input type="submit" value="Update Password" name="commit" />
Then in the controller you could add a case or if to check for that parameter.
def update
case params[:commit]
when "Update Password"
# Update the Password
when "Update Address"
# Update the address
else
# Default case
end
#...
end
The big downside here is that it couples the display HTML to the controller -- you can't change the button text without updating the controller.
I have a question on rails. I'm implementing a resource. The new/create actions are finished. Now I am working on the edit/update actions.
I want the user to have to input the password for the resource and then he can edit the values.
Either on two sites, so first "input password" and afterwards he can update the values, or just on one site where he has to enter the correct password and can update the values.
But I have no idea how to achieve this the best way.
Do you have any tips? I'm still a beginner.
Just have a :password field in params. Then in your controller you can do this.
def update
raise Unauthorized unless user.authenticate(params[:password])
#normal update stuff
end
You will have load the user and define what Unauthorized error is in this situations. You could also just render out and return a flash if you would rather go that route.
In the form you will just add an input field that
<%= f.text_field :password %>
I have an action in a controller that I call from two different views. In each case, I want the action to redirect back to the page on which the link was clicked. At the moment I am doing this...
In this view I am passing a parameter like this...
%a.showtooltip#wprofile{:href => idea_vote_up_path(#idea, :source => 'idea'), :title => 'Awesome idea - vote up !', }
and in the controller...
if params[:source] == 'idea'
redirect_to idea
else
redirect_to ideas_path
end
This works fine, but does not feel elegant, especially as it ends up being in a few actions. Is there a better way?
You can rewrite it in following way:
redirect_to params[:source] == 'idea' ? idea : ideas_path
If you want to redirect back to the page (refresh current page)
redirect_to request.referer
Store the referrer in the session like so session[:previous] ||= request.referer and use it as redirect_to session.delete(:previous)
I find that a good way is to have a hidden input with the value you'd like to be as the return url. Seems like an easily manageable solution and has worked for me. This way you can create the hidden input in 1 or 1000 views and have a single line of code in the controller to do the redirects. I can't immediately think of what the cons to this approach would be.
In form
hidden_field_tag(:redirect_to, params[:redirect_to]) # in the form that is to be submitted, value determined by a query string
hidden_field_tag(:redirect_to, "/a/direct/value") # in the form, value specified directly
In controller
redirect_to params[:redirect_to].presence || idea_path(#idea)
Didn't test the code and don't know ruby sups well so double check but the logic should stand. The ".presence" takes care of situations where you don't want a custom redirect to and have no hidden input to specify.
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| %>
from:
http://www.bingocardcreator.com/abingo/usage
#A view example with a block passed to ab_test:
<% ab_test("call_to_action", %w{button1.jpg button2.jpg}) do |button| >
<%= image_tag(button, :alt => "Call to action!" %>
<% end %>
Does whatever "choice" that gets passed in the block have to be some sort of link? How does a/bingo know when different choices have been converted?
The way Abingo works is to issue different options to different "identities" in a consistent manner so that the results can later be aggregated together again. There are several ways to do this, such as by IP address, by session_id, or by registered account, all of which are valid and can be used in conjunction. In effect, a particular identity will always get the same random selection of options.
An example from the documentation on assigning the identity is as a handler in ApplicationController:
before_filter :set_abingo_identity
def set_abingo_identity
if #user
# Assign identity based on user
Abingo.identity = #user.abingo_identity
else
# Assign identity for anonymous user
session[:abingo_identity] ||= rand(10 ** 10).to_i.to_s
Abingo.identity = session[:abingo_identity]
end
end
When you want to track action based on which A/B option was used, you need to inject calls in your controllers. Another example:
def show
# Track conversion for active Abingo identity
bingo!("show_info_page")
end
The mechanism by which the user navigates to that particular page is entirely arbitrary and can be by link, by form submission, by JavaScript redirect, or by clicking on an email. The only thing that matters is that the display of the A/B option and the later controller action that tracks the activity both have the same Abingo identity assigned.