Rails Routing with Unique User Identifiers - ruby-on-rails

When I create a user in my database, it also creates a unique identifier string.
In my routes, I have:
match '/users/:unique_identifer', :to => 'users#show'
This part is working fine. When I go to /users/xyz, it brings me to the show action for the appropriate user.
However, when I try to update the user record, it redirect me back to /users/SOMENUMBER where SOMENUMBER is the user's ID. This causes an error since the show action in the controller has:
def show
#user = User.find_by_unique_identifier(params[:unique_identifer])
end
In other words, the show action is now only looking up the user by their unique identifier and not the user id.
The update action is as follows:
def update
#user = User.find_by_unique_identifier(params[:unique_identifer])
if #user == current_user && #user.update_attributes(params[:user])
redirect_to #user
else
redirect_to #user
end
end
How do I get the update action to redirect to the user's show action but with the appropriate link (/users/unique_identifier) instead of /users/ID?

You should redefine to_param (and from_param to make life easier), it is used to generate links for objects, and by default use id field
so in your case
class User < ActiveRecord::Base
def to_param
unique_identifer
end
end
and now users_path(#user) should give you /users/your-uniq-identifier
here is nice description with examples:
http://apidock.com/rails/v3.1.0/ActiveRecord/Base/to_param

Try this
redirect_to user_path(#user.unique_identifer)
or
redirect_to :action => :show, :unique_identifer => #user.unique_identifer

Related

How do I restrict access to edit action and through URL entry?

I have a relationship user ("devise") that has many events.
I want to prevent users from editing events that do not belong to them and stop users from accessing the edit action by entering something like 'http://localhost:3000/events/65/edit' into the browser.
I also want to redirect the user back to the page they were on when clicking on the edit event link.
I tried the following two methods without success:
def edit
if current_user == #event.user_id
#event = Event.find(params[:id])
else
redirect_to events_path
end
def edit
#event = Event.find(params[:id])
unless session[:id] == #event.user_id
redirect_to events_path
return
end
end
If you only need this kind of authorization logic in this controller, something like this would be possible:
class User < ActiveRecord::Base
has_many :events
end
class EventsController < ApplicationController
def edit
#event = current_user.events.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to events_path, notice: "You cannot edit this event."
end
end
The rescue-block is really optional. If you remove it, the user will get a 404 Not found error message if she visits the edit URL for an event she didn't create,
If you expect to use authorization other places in your application, I would advise you to look into CanCan. It's a gem that sentralizes rules for authorization and access in an Ability class.
Try adding a before filter (it can be used for other actions as well if needed):
before_filter :check_user, :only => [:edit]
def check_user
#event = Event.find(params[:id])
unless current_user.id == #event.user_id
redirect_to (request.referrer || root_path)
return
end
end
The idea behind your first method is fine, but that comparison will always fail. current_user is a User object; #event.user_id is an integer (or possibly some form of UUID).
You need to either compare a User object to a User object:
if current_user == #event.user
Or an ID to an ID:
if current_user.id == #event.user_id

How do I get the ID of a model in a collection after it was created as part of a nested form?

I have a User model, which :has_many :widgets
Here is the 'edit' method:
def edit
#user = current_user
#user.widgets.build
end
I have an edit view which contains a user form and a widget form nested within it
In my UserController#update method, I save the data...all working as designed.
After save, I want to send the user to Widget#show with the ID of the widget that was just added to User.widgets
How do I get that ID?
Here is my update method:
def update
#user = User.find(params[:user][:id], :include => [:widgets])
if #user.update_attributes(params[:user])
redirect_to widget_path(????), notice: 'Your new Widget is ready' }
end
end
Couldn't you scope the #user.widgets.last.id request so that you are assured of getting their last widget, something like: #user.widgets.created_by(#user).last.id

Detecting whether to show or to process a form based on GET or POST

I've got a User model and an Account controller. When a user visits the /account url, it should show a form containing a text field with the username in it and a button to submit the form.
I have match '/account' => 'account#index' in my routes.
In my controller I have this method defined:
def index
#user = User.find(session[:user_id])
end
(Checking the user authentication happens in a before_filter)
Now the form shows correctly and is even populated correctly. However, I need to know how to tell whether the form has been submitted. What is the rails way? Do I have a separate route that watches for a POST request to /account? Or do I detect the request type in the index method? At what point do I decide whether the form has been submitted or not?
You could detect if the form has been submitted inside of the index controller. I believe the params hash gets sets the key :method to the method used for the request.
An alternative is to redo the way you route. Instead of match '/account' => 'account#index' you can do:
get '/account' => 'account#index'
post '/account' => 'account#post_action'
And then inside your controller you could do:
def index
#user = User.find session[user_id]
end
def post_action
#user = User.find session[user_id]
if #user.update_attributes params[:user]
flash[:notice] = 'Update Successful'
render :action => index
else
flash[:notice] = 'Update Unsuccessful'
render :action => index
end
end

Is it possible to update from an action/method other than the update action/method in Ruby on rails?

Is it possible to update from an action/method other than the update action/method?
For example in my users controller I already have an update method for other parts of my users account.
I need a separate one for changing my users password. Is it possible to have something like this:
def another_method_to_update
user = User.authenticate(current_user.email, params[:current_password])
if user.update_attributes(params[:user])
login user
format.js { render :js => "window.location = '#{settings_account_path}'" }
flash[:success] = "Password updated"
else
format.js { render :form_errors }
end
end
Then have my change password form know to use that method to perform the update?
It has 3 fields:
current password
new password
confirm new password
and I use ajax to show the form errors.
Kind regards
Yes you can:
Add this in routes.rb:
resources :users do
member do
put :another_method_to_update
end
end
In the view, you have to use the following URL:
another_method_to_update_user_path(#user)

Devise - Authenticate user (after validations) on a create action

Using Devise, I know how to protect controller actions from non-signed-in users through:
before_filter :authenticate_user!
In order to illustrate what I am trying to achieve, please see an example:
I have the following controller: (a project belongs to a user)
projects_controller.rb
def create
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
What I am looking for is a way that users can interact more with the website before having to sign up/sign in. Something like:
after_validation :authenticate_user!
if the user is not signed in, and redirect him after success (sign up/sign in) to the "project" show page.
Things I thought:
1.) Change the controller in order to accept a project object without user_id, ask for authentication if the user is not signed in, then update attributes with the user_id
I try to do it like this first and it results to a very ugly code. (Moreover authenticate_user! doesn't redirect to the #project which lead to more customization)
2.) Create a wizard with nested_attributes (project form and nested new registration form and session form)
3.) Something better? (a custom method?)
It seems authologic manages this more easily. I'm not sure it is a reason to switch so I would like to have your idea/answer on this. Thanks!
EDIT
references: Peter Ehrlich answer comment
CONTROLLER WITH VALIDATIONS LOGIC
projects_controller.rb
def create
unless current_user
#project = Project.new(params[:project]) # create a project variable used only for testing validation (this variable will change in resume project method just before being saved)
if #project.valid? # test if validations pass
session['new_project'] = params[:project]
redirect_to '/users/sign_up'
else
render :action => 'new'
end
else
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
end
def resume_project
#project = current_user.projects.new(session.delete('new_project')) # changes the #project variable
#project.save
redirect_to #project
end
routes
get "/resume_project", :controller => 'projects', :action => 'resume_project'
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
return '/resume_project' if session['new_project'].present?
super
end
Something like this should work:
def create
unless current_user
session['new_project'] = params[:project]
redirect_to '/register'
return
end
# and on to normal stuff
# in your devise controller
def after_sign_in_path
return '/resume_project' if session['new_project'].present?
super
end
# back in projects_controller now
def resume_project
#project.create(session.delete('new_project'))
# you know the drill from here
# I'd also put in a check to make an error if the session is not set- in case they reload or some such
Keep in mind that session is a cookie in the browser, and thus has a size limit (4kb). If you're posting images or other media, you'll have to store them temporarily server-side.
Another option would be to create a userless project, and use a similar technique to allow them to claim it as their own. This would be nice if you wanted unclaimed projects displayed to all to be available as a flow.
I haven't tested it out, but it should be possible to store the action the user was going to, I.e. create, with the params hash that was submitted and redirect to it upon successful login. It would then handle the error cases as normal.
Have you tried that?

Resources