How to determine which route was matched for a controller#action? - ruby-on-rails

Consider the example in the note for this guide on routing and singular resources Both of these would be directed to 'photos#index', but are different contexts.
/users/1/photos (might list a user's photos)
/photos (list all users' photos)
I want to give the user different options depending on which route was followed to access.

There are two ways to assign this issue.
1st way
In the users_controller.rb
before_action :set_user, only: [:photos]
def photos
#photos = #user.photos
render "photos/index"
end
private
#user = User.find(params[:id])
In routes.rb you need to add this route,
resources :users do
get "photos", on: :member
end
2nd way
In photos_controller.rb
before_action :set_user, only: [:photos]
def index
unless #user.nil?
#photos = #user.photots
else
#photos = Photo.all
end
end
private
def set_user
if params[:user_id].present?
#user = User.where(params[:user_id]).first
end
end

Well first of all, I'd make /users/1/photos an action off users controller instead. However, if you really want to do what you say there, you could check for presence of the user_id param on the photos controller index and fashion your finder appropriately.

For example you can validate route match with rspec, something like the following:
expect(:get => "/users/1/photos").to route_to(
:controller => "photos",
:user_id => "1",
)

Related

How do I edit an element from an index page if they are nested in another resource in Rails?

I need to edit the card element from the card index page, on this page I have only one parameter - the card ID. The decrease in the number of parameters was due to rewriting the route for the index page only.
I am new to Ruby and do not quite understand how I can find these parameter(board_id, column_id). Controllers with the function of updating the card are already there. How do I find these options correctly?
I have this routes:
get 'cards' => 'cards#index', as: :cards
resources :boards do
resources :columns, except: [:index, :show, :edit] do
resources :cards, except: [:index, :show]
end
end
end
So to edit the card element I need :board_id, :column_id, cards/:id:
board_column_card_path PATCH /boards/:board_id/columns/:column_id/cards/:id(.:format) cards#update
My cards_controller:
class CardsController < ApplicationController
def new; end
def index
#cards = Card.all.order(created_at: :desc).paginate(page: params[:page])
end
def create
#card = #column.cards.build(card_params)
#card.user = current_user
if #card.save
flash[:success] = "Card was successfully created."
else
flash[:error] = #card.errors.full_messages.join("\n")
end
end
def update
if #card.update(card_params)
flash[:success] = "Card was successfully updated."
else
flash[:error] = #card.errors.full_messages.join("\n")
end
end
Thank you for your help.
As I understand it, you want to update your card, with just your card id? This can be done easily by changing your routes. I recommned using shallow nesting: https://guides.rubyonrails.org/routing.html#shallow-nesting.
# inside your routes.rb file - you can add exceptions
# as in the code you have posted above if you wish
resources :boards do
resources :columns, shallow: true do
resources :cards, shallow: true do
end
end
end
# now because you are using shallow editing you can simply do this:
edit_card_path(#card.id)
And when you want to update, you can do so easily by just using the card_id. You will not need your the board and column ids. It is hard to be more specific because you have not given the card_params nor any forms.

Why am I getting a recordnotfound error when trying to access an instance in rails?

I have a user profile controller called "userinfo" and it's corresponding view. The userinfo index is the root path. In the homepage(which is the userinfo index), I have a link that takes you to the user profile page. It is giving me this error when I go to the home page:
My routes are:
My userinfos_controller:
class UserinfosController < ApplicationController
before_action :find_userinfo, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#userinfors = Userinfo.find(params[:id])
end
def show
#myvideo = Video.last
end
def new
#userinformation = current_user.userinfos.build
end
def create
#userinformation = current_user.userinfos.build(userinfo_params)
if #userinformation.save
redirect_to root_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#userinformation.destroy
redirect_to userinfo_path
end
private
def userinfo_params
params.require(:userinfo).permit(:name, :email, :college, :gpa, :major)
end
def find_userinfo
#userinformation = Userinfo.find(params[:id])
end
end
and my view is:
<%= link_to 'profile', userinfors_path(#userinfors) %>
My routes.rb file:
Rails.application.routes.draw do
devise_for :users
resources :userinfos do
resources :videos
end
resources :pages
get '/application/decide' => 'application#decide'
root 'userinfos#index'
get '/userinfos/:id', to: 'userinfos#show', as: 'userinfors'
end
Thanks for any help!
ok, there are multiple errors and you are not following conventions of rails, index is not for what you have used.
Index is used to list all the users and show for a particular one with id passed in params.
Your index path is, as you can see, /userinfos which is correct and it doesn't have any id with it but you are trying to find user with params[:id] which is nil and hence the error.
Lets try out this:
def index
#userinfors = Userinfo.all #pagination is recommended
end
In your index view,
<% #userinfors.each do |userinfor| %>
<%= link_to "#{userinfor.name}'s profile", userinfo_path(userinfor) %>
<% end %>
It should work now.
Please read routing and action controller to get the idea and understand the magic behind rails routing and mvc architecture..

Nested resource under users, I want only the current_user to be able to access

I have the following under my routes.rb:
resources :users do
resources :submitted_terms, only: [:index, :create, :show]
end
I only want the current_user (the logged in user) to be able to see their own submitted_terms in terms of the index and show views. They shouldn't be able to see anybody else's index and show views and other people shouldn't be able to see theirs.
I think I know how to implement this but it feels sort of messy to me. Any thoughts?
You can before_action filter.
before_action :correct_user, only: [#action for which you need this filer]
def correct_user
#submitted_term = SubmittedTerm.find(params[:id])
unless #submitted_term.user == current_user
flash[:notice] = "Insuffient privlage"
redirect_to #some path or render
end
end
you may need to change code or create new action(filter) for checking if user is logged in or not

How can I block url adress in Devise (Ruby on Rails)

I blocked display links on the show page:
<% if #post.user == current_user %>
links
<%end%>
but I can't block url adress for unprivileged users:
http://localhost:3000/posts/1/edit
What can I do?
It's good possibility to use Pundit gem (https://github.com/elabs/pundit).
Your policy will look:
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def edit?
post.user == user
end
end
And your controller's action:
def edit
#post = Post.find_by(id: params[:id])
authorize #post
...
end
What you're looking for is something called authorization
Authentication = finding out if a user is present
Authorization =
determining if they are able to perform specific requests
The answer by Sergei Stralenia is correct - you'll need to use one of the authorization gems -- Pundit and CanCanCan being two of the most popular -- to validate whether a user is able to edit a particular object.
In regard the routing, you'll not be able to remove the edit route, unless you separate it out into something like an admin namespace (I'll explain more in a second).
--
Sergei Stralenia's post showed you how to use Pundit, I'll show you CanCanCan:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, Post
else
can :read, Post
end
end
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def edit
#article = Post.find params[:id]
authorize! :edit, #article
end
end
Admin
If you wanted to make a post only editable in an "admin" area, you'd be best using something like the following:
#config/routes.rb
resources :posts, only: [:index, :show]
namespace :admin do
resources :posts, only: [:new, :create, :edit, :update, :destroy]
end
This way, you will literally have no way for a non-admin user to edit/update posts in the front-end. Instead, they'll have to go into the admin area and make it so that they are able to edit it in there...
#app/controllers/admin/posts_controller.rb
class Admin::PostsController < ApplicationController
#actions & authorization in here
end
Within the edit action on your controller, perform the same check - something like:
#post = Post.find_by( id: params[:id] )
unless #post.user == current_user
fail(ActionController::RoutingError, 'User cannot edit this post.')
end
You can simplify the error check into:
fail(ActionController::RoutingError, 'User cannot edit this post.') unless #post.user == current_user
I hope this helps!
I guess the best way to do this is to use before_filter in your posts controller, i.e.:
before_action :authorize_admin, only: [:show, :edit, :update, :destroy]
or:
before_filter :authorize_admin, except: [:show]
where :authorize_admin is the method that You have to define either in posts controller (to use for posts only) or in application controller (to use in all controllers), like this:
def authorize_admin
redirect_to :new_user_session unless current_user&&current_user.admin?
end

Namespace an admin area

As an example, lets say I'm creating a twitter(-ish) clone.
A User has_many Tweets
A Tweet belongs to a User
Users can create tweets
Users can edit and delete their own tweets
Users who are admins can edit and delete all tweets
Users who are admins can edit and delete users who aren't admins
Here's my tweets controller:
class TweetsController < ActionController
before_action :set_tweet, only: [:edit, :update, :destroy]
before_action :only_admins_and_owner, only: [:edit, :update, :destroy]
def edit
# edits the tweet
end
def update
# updates the tweet
end
def destroy
# destroys the tweet
end
private
### security
def only_admins_and_owner
redirect_to root_url unless current_user.is_admin? || current_user === #tweet.user
end
###
def set_tweet
#tweet = Tweet.find(params[:id])
end
end
My users controller:
class UsersController < ActionController
before_action :set_user, only: [:edit, :update, :destroy]
before_action :only_admins_and_user, only: [:edit, :update, :destroy]
def edit
# edits the user
end
def update
# updates the user
end
def destroy
# destroys the user
end
private
### security
def only_admins_and_user
redirect_to root_url unless current_user.is_admin? || current_user === #user
end
###
def set_user
#user = User.find(params[:id])
end
end
And here's my panels controller. Not sure if this is the right way to do it to be honest. What do you think of my naming conventions? I haven't called it AdminController because by panels (plural) I am refering to the multiple panels in the admin area, the user panel (for displaying all of the users and offering administration controls in the view) and the tweets panel:
class PanelsController < ActionController
before_action :only_admins
def users
#users = User.all
end
def tweets
#tweets = Tweet.all
end
private
### security
def only_admins
redirect_to root_url unless current_user.is_admin?
end
###
end
If you think this controller setup is okay, how should I configure my routes to use these actions?
My routes file:
MyApp::Application.routes.draw do
resources :users, except: [:index] do
resources :tweets, except: [:index]
end
end
Now the above works okay if I am a normal user CRUDing away at user and tweets but how should I namespace my Panels controller?
When an admin is viewing users in the panels controller's users view, I want the URLs to look like this:
/control_panel/users
/control_panel/tweets
and when editing in the admin area:
/control_panel/users/12/edit
/control_panel/users/12/tweets/142/edit
but when a user is editing their own user or tweet:
/users/12/edit
/users/12/tweets/142/edit
This is because the panels administration views are vastly different to the user views, but the functionality of editing, updating and deleting is identical so I want to use the already existing actions. Am I doing it right? Not sure how else I could do it, other than adding loads of actions to to the panels controller, def_user_update and def_tweet_update and so on for every single resource. Doesn't feel very nice...
So how should I configure my routes?
I guess I want to sort of create an optional namespace around my two nested routes...
Maybe a concern? If I do that, though, I get an uninitialized constant Panel error.
If you want to use the same controller, which is something I don't really recommend, you can use a scope.
resources :users, except: [:index] do
resources :tweets, except: [:index]
end
scope :control_panel do
resources :users, expect: [:index] do
resources :tweets, except: [:index]
end
end
This gets you all the routes that you want, and they all point to the UsersController and TweetsController
What I recommend instead is using a different controller for admins. You can achieve this with namespaces.
namespace :control_panel do
resources :users, etc: ...
end
You then keep site user's concerns in TweetsController and control panel concerns in ControlPanel::TweetsController in app/controllers/control_panel/tweets_controller.rb
You can read more about namespaces and routing here.

Resources