I have a Rails app where I have a show action with a form. This show action uses a view that checks if user_type == "admin" and displays a partial accordingly. For this show view, only if user_type = admin can access it.
Once the form is submitted, it triggers another action in the same controller (store). The issue I'm having is that when there is an error with the submission, store renders show without reference to the user, so the partial doesn't appear since user_type won't equal admin (since it can't find the user). However, since access to 'show' is protected, I'm wondering if there's a way I can check the url.
Example:
the form is on:
http://foo.com/bars/4/users/3
however, when it fails, the render url becomes:
http://foo.com/bars/4/users/store
the show.html.erb view has this:
<%= form_for([:bar,#user], :url => store_bar_users_path(params[:bar_id]), :html => {:name=>"users_form",:multipart => true,:class=> "standardForm"}) do |user| %>
<%= render :partial => "users/admin_options" if #user.user_type == "admin" %>
in the store action in the controller:
if !#user.save
render :action => "show" ,:layout => 'application'
else
So what I'm wondering is if I can change the conditional in:
<%= render :partial => "users/admin_options" if #user.user_type == "admin" %>
to somehow also also check the url for store_bar_users_path, or something like that - or perhaps the render in the store action is incorrect.
Store the user type in a session variable, set when the user logs in. Then you don't have to keep loading it, and it is visible to all controllers, views, and helpers. Use this variable to determine if you want to show the partial.
Related
I have a users index action that directs a person to different pages depending on the params. Currently when a user passes in search params and I render the custom view template, when a person clicks on a user it goes to their user edit page. I want the back button on the browser to go back to the custom view if thats where the user came from. However, it keeps going back to the users index regardless.
What should I be doing to make the browser back button go back to the last page a user came from?
My controller code is:
def index
#criteria = params[:criteria]
respond_to do |format|
format.js {
unless #criteria.blank? || #criteria.length < 3
#results = fire(#criteria)
render :template =>'admin/users/search_user', :object => #results, :locals => { :autocomplete => true, :criteria => #criteria }
else
#users = User.paginate :page => params[:page], :order => "users.created_at desc"
render :template =>'admin/users/users', :object => #users, :locals => { :autocomplete => true, :criteria => #criteria }
end
}
format.html {
#users = User.paginate :page => params[:page], :order => "users.created_at desc"
}
end
end
This is my understanding of the flow:
user clicks on users link
routes defaults to users index
action loads data and renders template
template displays
user types in search params
the html form makes the search term go in critera which goes in params
enter by default from browser submits
submit points to same action since theres no custom route?
since params has criteria the search results/template is rendered
click on user, takes you to user edit
click back button on browser, browser knows the page list to be the users index as last stop since search results are not a seperate URL and the HTML is rendered with Javascript?
need to get the browser to redirect to the search results template somehow
Thanks!
Try <%= link_to 'Back', 'javascript:history.go(-1);' %>. It will give you exactly same functionality as browser's back button.
Not sure, if I understand you correctly, but this could solve your problem, maybe
<%= link_to "Back", :back %>
What you're describing is basically friendly forwarding. You can't override the browser's default behaviour in that case (unless you want to go down a deep rabbit hole of JavaScript), but you could achieve something user friendly by following a similar pattern to this:
http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#sec-friendly_forwarding
Hope that helps, anyway.
I currently have a Ruby on Rails application with a typical User model. I would like to allow users to edit their own account information, and change their email or password. However, I would like to have the corresponding forms for this on two different pages.
There is a users_controller.rb which already has a show, new, create, and edit path. The problem is that using the edit path to update the User database would require all forms to be on the same page, which is inconvenient for users who only wish to update their email or password, and not both. Essentially, I need that edit path to be available on multiple pages.
Any suggestions?
Not possible. Only one action per named route.
You could use one named route plus an extra parameter to tell the controller which form (or parts of a form) to render:
named route path
edit_user_path(#user, :form => "info") mysite.com/users/1/edit?form=info
edit_user_path(#user, :form => "acct") mysite.com/users/1/edit?form=acct
edit_user_path(#user) mysite.com/users/1/edit
Then read this parameter and switch between templates depending on the value. No parameter will render the default view containing the full form:
def edit
# ...
case params[:form]
when "info"
render :template => "info_only"
when "acct"
render :template => "name_and_password"
else
render :action => :edit
end
end
I assume this is just for UX and not for security. All of these separate forms will PUT to the same update action, meaning all attributes accessible in one form (eg. password) will be accessible in another form.
Split the form up into separate .html.erb files (like email.html.erb and password.html.erb) and in your controller write something like:
def edit
...
if params[:email]
render 'email'
end
if params[:password]
render 'password'
end
end
Obviously the conditions can be whatever you want.
I have index method that displaying two forms Sign in and Sign up, that means user can create an account and a log in from same place.
so I have users controller with index method that displaying a view with Sign in and Sign up form with two partials one is _signin.html.erb and _signup.html.erb in index.html.erb.
Any Idea How can I handle new and create methods from users and sessions controllers (may be I can ignore new method)?
As long as each form is being rendered with the correct model object and/or the correct value to the :url option, each form should send the expected request (assuming you're rendering your forms with form_for).
For example, your sign in form should start with something like this::
<%= form_for :session, :url => sessions_path %>
#...
<% end %>
As long as a POST request (the default from form submissions) is sent to a "collections" resource (i.e. /sessions) it will route the request to the create action in your SessionsController or whatever you named your controller.
For signing up, you probably have something like this:
<%= form_for #user do |f| %>
# ...
<% end %>
The #user model object will assume the request should go to /users. Again this will call your create action in your UsersController.
Of course, all this is also assuming your config/routes.rb file is just declaring each resource with something like:
resources :users
resources :sessions, :only => [:create, :destroy]
You usually have each form in separate views such as
match 'sign_in', :to => 'sessions#new'
match 'sign_up', :to => 'users#new'
But if you only want to display these forms in an index.html.erb view then these routes are no longer necessary.
<%= form_for :signin,:url=>{:controller=>"yourcontroller",:action=>"signin"},:html=>{:id=>"signin_form"} do |f|%>
...
<%end%>
<%= form_for :signup,:url=>{:controller=>"yourcontroller",:action=>"signup"},:html=>{:id=>"signup_form"} do |f|%>
...
<%end%>
One simple workaround that I found was that if the two forms have at least one uniquely named parameter, then you can simply route the POST request to a single action. Then within the action check which parameter exists and execute the corresponding code. You end up having essentially two actions within one action in your controller.
def create
if params[:username] and !params[:name]
# You know that the user pressed submit on whichever form
# has a field that fills params[:username].
# So do the action with that form's parameters here
# i.e, login an existing user
respond_to do |format|
format.html {redirect_to '/home', notice: "Login successful"}
end
elsif params[:name] and !params[:username]
# You know that the user pressed submit on whichever form
# has a field that fills params[:username].
# So do the action with that form's parameters here
# i.e, create a new user
respond_to do |format|
format.html {redirect_to '/onboard', notice: "Thanks for signing up"}
end
end
end
Just be sure to have it configured in your routes.rb so that when a POST request comes from the page the two forms are on, it will direct to this action in this controller.
Hope this helps!
One thing you can do on my rap lyric explanation site is "like" explanations (once you're logged in):
http://dl.getdropbox.com/u/2792776/screenshots/2010-01-17_1645.png
I'd like to show the "Like" links to users who aren't logged in, and then, when a non-logged in user clicks "Like", show him a lightbox with a "Login or Register" form (like Digg / Reddit)
http://dl.getdropbox.com/u/2792776/screenshots/2010-01-17_1650.png
What's the best way to accomplish this?
Currently I'm using this approach:
Clicking "Like" POSTs to /annotations/:id/vote (the POST body indicates whether the user is liking or "unliking").
The vote Annotation controller action has a require_user before_filter that looks like this:
def require_user
unless current_user
store_desired_location
flash[:notice] = "You'll need to login or register to do that"
redirect_to login_path # map.login '/login', :controller => 'user_sessions', :action => 'new'
return false
end
end
user_sessions#new looks like this:
def new
#user_session = UserSession.new
respond_to do |format|
format.html {}
format.js {
render :layout => false
}
end
end
The problem is that the redirect doesn't seem to work correctly over javascript:
http://dl.getdropbox.com/u/2792776/screenshots/2010-01-17_1700.png
How do I get this to redirect correctly?
Also, is this the right general approach? Another thought I had was to attach a different handler to the "Like" links in javascript when there was no logged in user (but I don't think this method scales well to other actions that I'd like to handle the same way)
There's a few problems to overcome here.
Browsers in general do not allow redirecting to a POST request.
redirect_to doesn't preserve format without additional input.
Store location does not preserve form data.
All these problems can be solved by eliminating redirects.
Here is how I've handed it in the past:
Instead of redirecting in required_user, render. If a before filter redirects or renders the pending action is cancelled. (No need to return false either). Unfortunately going this route blurs controller boundaries. But allows for simple html fallback, and lends its self to DRYness.
The high level view of the new work flow will be:
Request to annotations#vote (POST)
required_user filter fails
render new session
submit login information and original POST data back to annotations#vote (POST)
new filter in vote captures session information and logs in. vote proceeds as expected. If login fails return to 3.
annotations#vote redirects/renders as it should
Start by reworking the require_user to render the user_sessions#new template.
def require_user
unless current_user
flash[:notice] = "You'll need to login or register to do that"
#user_session ||= UserSession.new
respond_to do |format|
format.html {render :template => 'user_sessions/new'}
format.js {
render :template => 'user_sessions/new', :layout => false
}
end
end
end
The #user_session ||= UserSession.new ensures we can return validation errors to the form.
Now we've got to beef up your user_session#new template so that it can remember the action. Also if you plan on using lightboxes, this should be a partial rendered rendered by relevant RJS or the new.html.erb.
First we create a partial to create hidden fields preserving the POST data that would have been lost in a redirect:
<% if params[:controller] == "annotations" %>
<% content_for :old_form do %>
<%= hidden_field_tag "annotation[song_id]", params[:annotation][:song_id] %>
<%= hidden_field_tag "annotation[vote]", params[:annotation][:vote] %>
<% end %>
<% end %>
Then render that partial in the login partial that will occupy your lightbox:
<%= render :partial => vote_form_replica %>
<% url = params[:controller] == "user_sessions ? user_sessions_url : {} %>
<% form_tag #user_session, :url => url do |f| %>
<%= yield :old_form %>
<%= f.label :user_name %>
<%= f.text_field :user_name %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= submit_tag %>
<%end%>
The empty hash for url in the form_tag looks like an error, but isn't. It ensures that the form data is posted to the url that rendered the form. Which at this point should be annotations/:id/vote
Now for the new filter to login. Essentially it will be doing what ever UserSessionsController#create does without the render/redirect. The following is copied from the RESTful authentication plugin.
def authenticate
self.current_user = User.authenticate(params[:login], params[:password])
if logged_in?
if params[:remember_me] == "1"
current_user.remember_me unless current_user.remember_token?
cookies[:auth_token] = { :value => self.current_user.remember_token,
:expires => self.current_user.remember_token_expires_at }
end
end
end
All that's left is to make sure the filter order is right.
before_filter :authenticate, :require_user, :only => :vote
N.B.: You're probably not going to use this version of require_user without this version of authenticate so it makes sense to combine them into a single filter.
And that's it. The way this has been set up allows for robust DRY easily reuseable code. By placing the new filters into ApplicationController they're available in any controller. From this point, adding this functionality to any other controllers/actions takes only 3 simple steps:
Create a new partial modelled after the vote_form_replica partial.
Add the corresponding render statement to the new session template.
Apply the filters to your actions.
I would approach this in the way you describe at the bottom of your question. Before displaying the page initially, check if the user is logged in. If they are, the "Like" links should use their normal behavior. If not, bind a click event to show the register/login panel. There's nothing about this that can't be reused. In fact, we use this exact method at my job. Any user action that requires authentication either follows its normal behavior or pops up a generic login panel depending on login state at the time the page loads.
How can I render after executing an action in a restful controller instead of redirecting.
I have a controller with standard actions, and I added a special action that adds data to the resource in question, via a form on the #show page (Think comments on a post). I am validating the input from the form and want to re-render the show action on error and redirect to the show action on success.
I want to render to save the user from inputting their info twice, but when I try to render the show action with an error in the flash[:notice] I get an error saying that I am not specifying an ID. When I do specify an ID, it tries to render a new template that doesn't exist yet.
I am thinking that it should be a as simple as:
def add_comment
if my_validation?
save the object
redirect_to :action => "show", :id => params[:id]
else
render :action => "show", :id => params[:id]
end
end
This is not my actual code, just something I put together just now as an example.
The best way is to re-render the :new
def create
#obj = TheObject.new(params[:object])
render :action => :new unless #obj.save
end
And in the new.html.erb
<% form_for :obj,
:url => object_url(#obj), :html => {:method => :post} do |f| %>
<%= f.text_field :name %>
<% end %>
That way, the inputs in the form will be pre-filled with what the user entered.
Create a new data object and add the values from the form, before you rerender, think it would work then. If you still get problems, try setting a boolean for editing new vs. existing rows, or create two different views entirely.
I've done it before but I don't quite remember how. Sometimes when I used the very typical use of the MVC pattern, it was allmost "automagical", othertimes (as I had to use an old quirky database) I had to code all the magic myself; sometimes usin the .new? function (or what it was called) on the ActiveRecord object, othertimes I used temporary "magic values" for ID (typically alphabetic strings for invalid id values.
(I appologize if I made some mistakes, it's a while since I coded Rails code...)