Nested routes with devise - ruby-on-rails

So currently im trying to make an app where each user has its own todo list with its own index page, which means, everybody is able to visit the user page to see each tasks of the user.
I use devise and created a simple model with a user reference:
rails g model Todo title:string completed:boolean user:references
added of course belongs_to / has_many to todo.rb/user.rb
Now since i want each todo index page to be assiociated with the users todos, i've created a nested resource like so:
resources :users do
resources :todos
end
visiting
http://localhost:3000/users/1/todos/
works fine and shows the index page.
Heres the problem: when i change the number after /users/ to, for example, 2, its still working, even though there is no user with the id of 2.
Any ideas how i can make this dynamic, so that the integer after /users/ represents the user_id? Thought i did it right but i guess im missing something. Thanks in advance!
EDIT:
as requested, TodosController.rb:
class TodosController < ApplicationController
def index
#todos = Todo.all
end
def new
end
def show
#todo = Todo.find(params[:id])
#user = #todo.user
end
def update
end
def edit
end
end

Let look at your index action:
def index
#todos = Todo.all
end
It displays all todos always, because it doesn't know anything about the user.
It should be:
def index
#user = User.find(params[:user_id])
#todos = #user.todos
end
And in the show action you have to find the user at first, in this case you're sure, that the requested todo belongs to the requested user
def show
#user = User.find(params[:user_id])
#todo = #user.todos.find(params[:id])
end
You can refactor out #user = User.find(params[:user_id]) to the before_action callback, because you'll use it in all actions

Related

What's the best way to run some conditional if the next action is anywhere but the place you want the user to go?

I'm currently working on a personal project but stuck while trying to figure out the best way to hook into a particular point in the request cycle.
So what I want to accomplish is: from the "new" action (inside the child object's controller), I want to delete the associated parent object if the user lands up anywhere else but the "create" action as the next action. So far I'm thinking I want to write a private method that calls destroy on the parent object as a conditional if next action is anywhere else but "create" but I can't seem to find the right callback that precisely fits my particular situation. Can anyone suggest a good solution?
Here's my code to the respective controllers.
Parent Controller:
class EncountersController < ApplicationController
before_action :verify_if_logged_in, :authorize_provider, :set_patient_by_id, :view_all_patient_encounters
def index
#encounter = Encounter.new
end
def create
#encounter = Encounter.new(encounter_params)
#encounter.provider = current_user
#encounter.patient = #patient
if #encounter.save
redirect_to new_patient_encounter_soap_path(#patient, encounter)
else
render :index
end
end
private
def set_patient_by_id
#patient = Patient.find_by(id: params[:patient_id])
end
def view_all_patient_encounters
#encounters = #patient.encounters
end
def encounter_params
params.require(:encounter).permit(:encounter_type)
end
end
Child Controller:
class SoapsController < ApplicationController
before_action :set_patient_by_id, :set_encounter_by_id
def new
#soap = SOAP.new
end
def create
#If I don't end up here immediately after the new action for whatever reason
#I want to destroy the newly created parent object
end
private
def set_patient_by_id
#patient = Patient.find_by(id: params[:patient_id])
end
def set_encounter_by_id
#encounter = Encounter.find_by(id: params[:encounter_id])
end
end
Once I get to the new action of my SoapsController from the create action of my EncountersController, I want to destroy the associated Encounter object if I don't end up in Soaps#create as my next action.

How do I get acts_as_votable results for a specific model?

I'm working with the acts_as_votable gem for a project that will allow users to 'like' their favorite courses and their favorite guides. The favorited guides will then show up on one page and the favorited courses on another. I'm having trouble with retrieving model specific results in my controller below is code that works but is not scoping to a specific controller.
class FavoritesController < ApplicationController
def guides
end
def courses
user = current_user
#courses = user.find_up_voted_items
end
end
This is the only code I've gotten to work, I realize there is nothing in the controller currently to narrow down the results to a specific model but I wasn't able to get anything I tried to work.
From the acts_as_votable docs:
Members of an individual model that a user has voted for can also be
displayed. The result is an ActiveRecord Relation.
#user.get_voted Comment
#user.get_up_voted Comment
#user.get_down_voted Comment
https://github.com/ryanto/acts_as_votable
So in your case I would use:
class FavoritesController < ApplicationController
before_action :get_user, only: %i[guides courses]
def guides
#guides = #user.get_up_voted Guide
end
def courses
#courses = #user.get_up_voted Course
end
private
def get_user
#user = current_user
end
end

saving a model with multiple associations

I have a model called theaters which stores info on theaters.
I list the theaters based on a zipcode searched for by the user. The user can then click on an individual theater and show reviews for it. If they are logged in they can add a review.
So my models are:
Theaters has_many reviews
User has many reviews
Reviews belongs to user and has one theater
When submitting the new review I am doing this (#theater_id is passed as a param via the add review submit button):
def create
#user_id = current_user
#review = Review.create(review_params.merge(:user_id => #user_id,:theater_id => #theater_id))
if #review.save
redirect_to #review
else
render 'edit'
end
end
I feel like because of my associations there is an easier more rails-correct way to do this. Like rails should automagically put in my current user id and theater id... right?
Thanks
Nested Resources
I don't know how your routes are set up, but this is typically within the realm of nested resources, where your reviews objects will be created under theatre objects:
#config/routes.rb
resources :theatres do
resources :reviews #-> domain.com/theatres/1/reviews/new
end
When you use routing like this, it sets an extra param in your controller, called theatre_id, which you can use to populate your new Review object:
#app/controllers/reviews_controller.rb
Class ReviewsController < ApplicationController
def create
#review = Review.new(review_params)
#review.save
end
private
def review_params
params.require(:review).permit(:your, :params).merge(theatre_id: params[:theatre_id]
end
end
--
Alternatively, you could set the theatre object of your new ActiveRecord object:
#app/controllers/reviews_controller.rb
Class ReviewsController < ApplicationController
def create
#theatre = Theatre.find params[:theatre_id]
#review = Review.new(review_params)
#review.theatre = #theatre
#review.save
end
end

Rails file setup for a control panel?

How do you setup your views, controllers and routes?
One controller for everything the control panel does, or many?
Firstly, let's try to think how we would view the various panels. Let's say our control panel is pretty simple. We have one panel to show all the users who have signed-up and can CRUD them, and another panel to show all of the images that have uploaded, and we can carry up CRUD on those too.
Routes:
scope path: 'control_panel' do
get 'users', to: 'panels#users', as: :panel_show_users
get 'photos', to: 'panels#photos', as: :panel_show_photos
end
Controller:
class PanelsController < ApplicationController
def users
#users = User.all
end
def photos
#photos = Photo.all
end
end
View file structure:
panels
|_ users.html.erb
|_ photos.html.erb
Okay, now I don't see any problems with that, to simply access the panels and populate the views with data. Do you see any problems?
Here is where I'm sort of at a cross roads though. What should I do when I want to Created Update and Delete a user/photo? Should I put them all in the PanelsController?
class PanelsController < ApplicationController
before_action :protect
def users
#users = User.all
end
def update_user
#user = User.find(params[:id])
#user.update(user_params)
end
def photos
#photos = Photo.all
end
def update_photo
#photo = Photo.find(params[:id])
#photo.update(photo_params)
end
private
def protect
redirect_to root_url, error: 'You lack privileges!'
end
end
While this would result in a large PanelsController, it would feel good to be able to execute that protect action and just one controller hook. It would also mean the routes would be easy to setup:
scope path: 'control_panel' do
get 'users', to: 'panels#users', as: :panel_show_users
post 'user', to: 'panels#update', as: :panel_create_user
get 'photos', to: 'panels#photos', as: :panel_show_photos
post 'photos', to: 'panels#photos', as: :panel_create_photo
end
I should use resource routes here?
Like I say, this will result in a huge panels controller, so I was thinking it may be better to have a separate controller for each resource and then redirect to panels views?
Routes:
scope path: 'control_panel' do
resources :users
resources :photos
end
Controllers:
class UsersController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def update
end
def destroy
end
end
class PhotosController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def update
end
def destroy
end
end
Still some quirks though. I have my Users#index action there, but what if I have two routes that return an index of all users? In the control panel, but also, when people are searching for another user, for example. Should I have two routes in the User controller? def public_users and def control_panel_users? That may be the answer. Could setup a hook to run #users = User.all in both of them, but redirect to a different location, and not have the protect method redirect them.
How should I protect these routes from non-admins? Should I move my protect method into the the application controller? Wouldn't this be a bit fiddly to setup?
class ApplicationController < ActionController
before_action :protect
def protect end
end
class StaticController < ApplicationController
skip_before_action [:home, :about, :contact]
def home
end
def about
end
def contact
end
end
But that is my question. 1 control panel controller or no control panel controller.
I really wish there was more advanced tutorials out there :( Billions of books on CRUD, MVC and things, but nothing on advanced things like control panels and AJAX...
Don't have a control panel controller. And to protect stuff from non-admins, use namespacing - read more about it here: http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
You can protect your 'admin'-namespaced controllers with authentication, and have the non-namespaced controllers open to the public (or open to non-admin users)
With regards to your def public_users and def control_panel_users question, you could just have two def index methods - one in the non-namespaced controller, and one in the admin-namespaced controller. They would each do different things.
So, you'd have 4 controllers in total:
2 non-namespaced, one for users, one for photos (containing all public stuff)
2 admin-namespaced, one for users, one for photos (containing all control panel stuff)
If you wanted, rather than using 'admin' as the namespace, you could use some other term you prefer - like 'panel'. 'Admin' is pretty conventional though.

Wrapping within filter

Using Rails 3.2. I am trying to secure my app by checking user permission on all crud actions. Here is one of the examples:
class StatesController < ApplicationController
def create
#country = Country.find(params[:country_id])
if can_edit(#country)
#state.save
else
redirect_to country_path
end
end
def destroy
#country = Country.find(params[:country_id])
if can_edit(#country)
#state = State.find(params[:id])
#state.destroy
else
redirect_to country_path
end
end
end
class ApplicationController < ActionController::Base
def is_owner?(object)
current_user == object.user
end
def can_edit?(object)
if logged_in?
is_owner?(object) || current_user.admin?
end
end
end
I notice that I have been wrapping can_edit? in many controllers. Is there a DRYer way to do this?
Note: All must include an object to check if the logged in user is the owner.
You are looking for an authorization library. You should look at CanCan which allows you to set certain rules about which objects can be accessed by particular users or groups of users.
It has a method, load_and_authorize_resource, which you can call in a given controller. So your statues controller would look like:
class StatesController < ApplicationController
load_and_authorize_resource :country
def create
#state.save
end
def destroy
#state = State.find(params[:id])
#state.destroy
end
end
CanCan will first load the country and determine whether or not the user has the right to view this resource. If not, it will raise an error (which you can rescue in that controller or ApplicationController to return an appropriate response".) You can then access #country in any controller action knowing that the user has the right to do so.

Resources