#user vs current_user in a view - ruby-on-rails

I'm still kind of new to Rails and I'm not understanding why I cant access #user in a custom view template. But it works perfectly fine when it's being called in one of the standard actions like show, edit, update, destroy
for example I created this route
get 'mygroups' => 'users#mygroups'
In the users_controller I added
def mygroups
#user = User.find(params[:id])
end
then I created a new view template called mygroups.html.erb under Users and added this to the view
<%= #user.first_name %>
I get an error saying
Couldn't find User with 'id'=
I have a current_user object though that I can use in the view without any problem.
<% current_user.first_name %>
I just don't understand why I can't load the #user object in my mygroups method to get it to show in the mygroups.html.erb template. Are there any drawbacks to using current_user instead of #user ? how come the users controller has show, update, etc... loading #user by calling #user = User.find(params[:id]) but it doesn't in my method?

I'm guessing when you're accessing the mygroups route, you're not passing in an id, hence the error message saying it's blank. The default CRUD user actions (show, update etc) will all have routes setup to expect the id, and any links to those actions (or form URLs etc) will carry through the user id in question.
The difference between #user and current_user in this sense, is that current_user is generally your authenticated user, based on the current session. However, #user in the users controller is the user being acted upon. Imagine that you have an app where your current_user is some kind of admin, and that admin is allowed to create, read, update and destroy other users. The #user being acted upon is a specific user, rather than the currently authenticated one.
Try accessing your mygroups route with an id, /mygroups?id=USER_ID if you're sure you want it to work for any user - if you only want it to work for the current user, lose the User.find lookup and just use current_user instead.
Hope that helps!

current_user is almost always (99% of the time) the current logged in user. This method is defined (behind the scenes in your case) in ActionController.
Your error message is indicative of not having an id param present, whether by POSTed parameters or parameters in your query string.
Since you're new to Rails, you may find the classic Rails Tutorial a great and beneficial read. https://www.railstutorial.org/

Related

Rails RESTful controller action to create User

I am re-reading railstutorial the second time for clarity. I was trying to understand RESTful resources provided by rails where it listed various HTTP request with corresponding controller actions: index, show, new, create, edit, update, destroy.
I never really fully understood how rails controller works and I wanted to clarify it. If I make a users_controller.rb and want to create new user, and that's it. I don't care about redirecting, flash messages, and other flairs. I just want to create new user and save it on my database.
I want to do User.create(name: "Iggy1", email: "letsmail#iggy.com"). Assume I did migration and I have name (string) and email (string) on DB schema.
What is the least code line on users_controller.rb needed to create a user?
The reason why I ask this question is up til now, I am still not 100% sure which code line actually performs the desired action. Is rails actually smart enough to recognize these specific keywords index, new, create, destroy, show, edit, and update, or are they arbitrary? If rails is smart enough to detect those seven keywords, by merely listing the method (def destroy; #<no code>; end), is DELETE user method automatically accessible, or I have to actually say def destroy; User.find(params[:id]).destroy; end to use DELETE user method? On users_controller.rb, I have, from railstutorial, in the end, this elaborate code.
...
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
...
Is merely having def create on Users_controller sufficient for rails to create a user?
def create
end
Or I need at least to have User.new, or more codes to create new user?
The first thing you'll need to create a user is a 'new' action in your UsersController, like the following:
class UsersController < ApplicationController
def new
#user = User.new
end
end
When you declare the above method definition, Rails will expect a file named 'new.html.erb', unless you tell it to render another template.
NOTE: I'll continue under the assumption that you don't want to do this, but if you did, you would add render 'other' to the end of your 'new' method (where 'other' is the file 'other.html.erb'):
Since your goal is to create a new User instance, this view template will need a form to add the name and email fields. While not required for all Rails forms, one common way of creating a User is to create a 'new' (i.e. unsaved) User instance and pass it to the form, so that the form knows which attributes a User has. Rails passes this instance variable to the view, and it also knows which view file to render (because the 'new' method you defined should be named the same thing as the 'new.html.erb' view template that contains the form). This is also how Rails knows that the 'submit' button of the form should read 'Create user'.
Assuming you have the above, the next step is adding a place for the form data to be sent once you click the form's "Submit" button. This is what the 'create' action does. In other words, the 'new' action is in charge of displaying the form in your browser, and the 'create' action is in charge of handling the data submitted by that form. The bare minimum code you'll need to add at this point is the following:
def create
#user = User.create(user_params)
end
The way Rails does this is through a special method it implements, called 'params'. Behind the scenes, Rails takes the HTTP request that occurs when you submit the form, and stores certain data in an ActionController::Parameters object. This data is then processed by the 'user_params' method (which you'll need to declare; see below), and only the paramaters which you whitelist in that method definition (i.e. 'name' and 'email') are returned.
This implies that you'll need to implement that 'user_params' method definition. This is typically done as a private method at the bottom of your UsersController, since you don't want this method available outside of the controller. So you'd add the following:
private
def user_params
params.require(:user).permit(:name, :email)
end
This is important from a security standpoint because a malicious user could potentially add new form elements in their browser (for instance, an element named 'admin') and click 'submit'. If your User model does indeed include an 'admin' attribute, that malicious user has just created an Admin user (with corresponding Admin privileges).
Finally, since the request that the form sends is a POST request (not a get request), Rails expects you to provide a view to send the user to after they submit the form. This view is typically the 'show.html.erb' template for the user you've just created, since the 'create' doesn't have a template of its own (remember, the form we've discussed is the view for the 'edit' action, not the 'create' action). The 'show' view is rendered by the following method:
def show
end
In addition, you'll need to tell Rails to redirect to the 'show' page after the 'create' action is finished. Add this to the end of your 'create' method:
redirect_to #user
Since you passed a single instance of the User class, Rails infers that you want to redirect to the 'show' action (as opposed to 'index' or some other action) of the User controller. Depending on what you pass to the "redirect_to" method, you can send your user to any number of destinations. See these docs for more info.
So in summary, besides the 'edit.html.erb' and 'show.html.erb' view templates and the route declaration in your 'config/routes.rb' file (which I haven't discussed in detail, since the question scope is limited to the UsersController), the bare minimum amount of code your UsersController should have is the following:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.create(user_params)
redirect_to #user
end
def show
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
You have to understand rails is built on MVC and REST. MVC is an architectural pattern to distribute responsibility of your project.
M - Model, which interacts with the database and the ideal place to implement business logic.
V - View, where the HTML rendering happens
C - Controller, basically bridges the communication between M and V.
So, basically when the end user accesses your domain the request comes to the webserver and then, to the rails API. The rails API would know the default controller to transfer the request and the default controller action would return the html output.
Default router in config/routes.rb
root to: 'controller#action'
Likewise rails understands the 5 HTML methods which are GET, POST, PUT, DELETE and PATCH. So, once you create a controller and set the reference in routes.rb as resources [controller_name] then, the rails routes would create 8 urls for each of the 8 default actions in your controller. For an example let's say your controller is users_controller.rb then, you set it in routes.rb as resources :users this would allow the following methods,
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
So, when you build your view and set the form_for url as users_path and set the http method as POST the submission of the form would trigger the create action. So, in the create action you should write all the code that is needed to trigger the model to save your record.
Also, go through this document as well,
http://guides.rubyonrails.org/routing.html

RESTful routing best practice when referencing current_user from route?

I have typical RESTful routes for a user:
/user/:id
/user/:id/edit
/user/:id/newsfeed
However the /user/:id/edit route can only be accessed when the id equals the current_user's id. As I only want the current_user to have access to edit its profile. I don't want other users able to edit profiles that don't belong to them.
What is typically the best practice to handle this situation?
Should I leave the route as is, and thrw an error if the current_user.id != param[:id], forcing the front end client calling the api to track the logged in user's id?
Should I make a special route /user/self/edit and in the controller check to see if param[:id] == 'self'?
I would've added special routes for current user profile actions, in this case you don't have to check anything. Just load and display the data of current user. For example:
/my-profile/edit
/my-profile/newsfeed
It's not that RESTful but you don't have to put extra checks keeping your code clean.
If you still have to have (or want to have) a strict RESTful routes then I would use a before_filter and check if the id = current_user.id. If not then return 401 or 403.
I only want the current_user to have access to edit its profile. I
don't want other users able to edit profiles that don't belong to
them.
What I suggest is to use some authorization gems like pundit
Sample code:
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
def edit?
#current_user == #user
end
end
Also with an authentication gem like Devise, only the current_user(the users who logged in) can only access and edit their profiles
I would say that you are doing it correctly, just keep your current route as it is right now. And what you should do is to add a restriction in your controller instead. I would assume that you are using Rails, and working on users_controller.
class UsersController < ApplicationController::Base
def edit
if current_user.id == params[:id]
# do your work
else
render :404
end
end
end
Or you could clean up your controller by moving the restriction into a callback instead:
class UsersController < ApplicationController::Base
before_filter :restrict_user, only: [:edit]
def edit
# do your work
end
private
def restrict_user
render :404 unless current_user.id == params[:id]
end
end
You can add the gem "cancancan" and after the initialize....
class Ability
include CanCan::Ability
def initialize(user)
can :update, User do |user|
user.id == params[:id]
end
end
end
Then add this authorize! :edit, #user to your update action
You're going to need to add authorization code in all the user_controller methods as another comment suggested. Usually what I do in apps where a user is only supposed to edit their own profile I add a /profile route for a user to edit their own profile and then on the main /users/:id/* routes I add logic to prevent non-admin users from accessing those routes.
User is able to view his profile /users/1 or edit his profile /users/1/edit. From users perspective this URLs are absolutely fine.
There is no links which may lead user to edit the another user. You are trying to cover the different situation: when someone manually trying to craft the URL and get access to another account. I would not call them hackers, but technically they are – users who are trying to exploit your website to pass the restrictions.
You don't have to worry about "hackers" convenience. I'm always use current_user in edit action so nobody can edit wrong profile whatever his profile is.
def edit
#user = current_user
end
Also, I need to mention that you should also cover update action with such checks. With edit you may only get data (and probably only wide-public open data, unless you put billing information or plain-text-passwords inside your edit template). But with update you can actually change the data, which may be more destructive.
Because it seems that the only available user resource should be the authenticated user, I think the best way to solve this is
GET /user
PUT /user
GET /user/newsfeed
If you like to extend the api usage in future so that one user could have access to other user resources, than you need a solution that includes the user ids. Here it makes sense to introduce the routes for "self", too. But then you also have to implement an access check on server side.
GET /user/id/:id
PUT /user/id/:id
GET /user/id/:id/newsfeed
GET /user/self
PUT /user/self
GET /user/self/newsfeed
But I think you should keep it as simple as possible
For further investigations I would propose books like http://apigee.com/about/resources/ebooks/web-api-design which give a good introduction into API design
Since you only care to provide RESTful endpoints only for the currently authenticated user, which is available in your controllers as current_user, i say you don't need the id identifier parameter. I suggest using the following routes:
GET /user => users#show
PUT/PATCH /user => users#update
GET /user/edit => users#edit
You should keep the url as it is. Authentication and Authorization are separate concerns. 'current_user' refers to the user who is authenticated to access the apis. The id in the url identifies the resource on which 'current_user' is working, so does he have access to that resource or not is the concern of authorization. So you should add current_user.id != param[:id] (as you mentioned) in your api permissions and throw 403 status code in response.
You should use this route:
PUT /user/me
Note that there is no need for "edit": you should use the PUT method instead.
Also, you should explicitly define the route I've written above, instead of checking if id == 'self'.

Security - Checking if current user exists before executing code

In one of my controllers I have this method:
def method_name
if current_user
#model = Model.find(params[:id])
if #model.destroy
flash.alert = 'Model deleted successfully'
redirect_to models_path
end
end
end
I check if there is a current_user assigned by devise before giving the ability for the #model to be deleted. Is this safe and sufficient in terms of security?
What I really do is just checking if current_user exists. So is there a way that somebody can "trick" the system that current_user does exist and as a result be able to trigger the commands included in the method?
You will get a spectrum of answers in this. But if you want the user to be logged in then just do this at the top of your controller:
before_filter :authenticate_user!
That is provided by devise and ensures that there is a logged in user before allowing any controller actions.
If you have simple authorization then yes, most likely though you are going to want to make sure that the user has the authorization to delete the object. You can do that several ways. My favorite one right now is the Pundit gem.
You could also just check that the user owns the object in order to be able to delete it. That code would look something like this
#model = Model.find(params[:id)
if current_user.id == #model.user_id
# Rest of your destroy code
end

Ruby on rails passing information from session into db

I have been trying to set up a little twitter app to teach myself ruby on rails, i have a log in page that works and stores the user like this
session[:user_id] = user.id
I can use that in the views, but i am trying to create a relationship based on it so that the tweet, which is created on a new page in in a basic form, is linked to the current session user.
I for some reason can't figure out how to do it.
You can have the current_user (for example) method:
def current_user
#current_user ||= User.find_by_id(session[:user_id])
end
If you already have this, you can create post with:
#post = current_user.posts.create(params[:post])
You should also check, of course, if any user is signed in before creating post, or you'll have NoMethodError.
See devise gem https://github.com/plataformatec/devise
Devise provided current_user and user_session methods in your views and controllers

Correcting invalid Resource Routes in Rails

One thing I noticed when working with nested resource routes in Rails is that it is technically possible for a user to visit a route where the child resource exists (and is therefore displayed correctly), but the id for the parent resource represents an object that is not actually related to the child resource.
For example, in the route users/:user_id/post/:id, the user could type in a route where :user_id represents a user who did not make the post corresponding to :id.
What would be the best way to fix this so that if the user visits an invalid URL, the server redirects the user to the correct URL?
I have already put some code in my controllers to handle this, but it's kind of awkward having to check the path in every controller action and then redirect the user to the appropriate url, especially since the URL helpers are different for every action.
(edit_user_post_path(#user, #post), new_user_post_path(#user, #post))
There has to be a better way, right?
You should have a before_filter running on all requests that makes sure the user is valid. If not, it will throw ActiveRecord::RecordNotFound and show the friendly 404 page.
Then grab the post based on the user however you need, whether in another before_filter or directly in the action. Base your post search on the user. My example below demonstrates doing this with another before_filter.
before_filter :find_user_by_user_id
before_filter :find_post
def show
# Use #post variable here however you need
end
private
def find_user_by_user_id
#user = User.find(params[:user_id])
end
def find_post
# This assumes you have an association set up as needed
#post = #user.posts.where(id: params[:id]).first
if #post.nil?
# Do whatever you need here
end
end
First of all you should know that the error wich is raised by ROR will display the message 'Sorry but the page you are looking for does not exist' on a production environment.
Therefor I would not be concerned about that. if you want to 'capture' the failure and quickly redirect to a safe area you might be interested in using the rescue method.
http://www.simonecarletti.com/blog/2009/12/inside-ruby-on-rails-rescuable-and-rescue_from/
have fun

Resources