Adding a wildcard to Rails route - ruby-on-rails

In my Rails 4 project, I have a route like this (via rake routes) :
user_articles_path GET /users/:user_id/articles(.:format) articles#index
This works beautifully so that /users/1/articles shows user 1's articles.
What's the simplest way to get all articles from all users?
Something like /users/*/articles or /users/all/articles would be sweet.
Here's the Articlescontroller index method:
def index
#articles = if params[:user_id]
user = User.find(params[:user_id])
user.articles.where('content like ?', "%#{params[:search]}%").page(params[:page]).per_page(5)
else
current_user.articles.where('content like ?', "%#{params[:search]}%").page(params[:page]).per_page(5)
end
end
I show articles based on a user/id and then if a user is logged in (via Twitter) I show them their articles.
Update
Not sure if this is too hacky, but here's my current index method. It works like I want, but it could probably be simplified:
def index
if current_user
if params[:user_id]
user = User.find(params[:user_id])
#articles = user.articles.where('content like ?', "%#{params[:search]}%").page(params[:page]).per_page(5)
else
#articles = current_user.articles.where('content like ?', "%#{params[:search]}%").page(params[:page]).per_page(5)
end
else
#articles = Article.all.where('content like ?', "%#{params[:search]}%").page(params[:page]).per_page(5)
end
end
Articles are there for logged in and logged out users with easy routing to show a specific user's articles. Suggestions for cleaning it are welcome.

The best way is to use the Article as a resource in your route file:
resources :articles
Doing this you will have to create an ArticleController and with the path /articles you'll be redirected to a new set of views that will load all the articles without handling the user they come from. Eg. in the index action of this controller:
def index
#articles = Article.all
end
This is the best way to handle a REST interface in Ruby, since you are querying the Article resource alone here, and you should not pass through the User resources.

Related

Nested routes with devise

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

Letting admin see all posts by all users in rails

Rails and programming noob here. I am a teacher trying to create a god mode where I can view/edit etc posts of my students, however, students should be only able to view their own posts with this in the posts controller:
def index
#posts = Post.where(user_id:current_user)
end
But how do I create an index for me to see everything?
Create an AdminController with the following index method.
def index
if params[:user_id]
#posts = Post.where(user_id: params[:user_id])
else
#posts = Post.all
end
end
This method will show posts of a certain user if the url parameter user_id is set and shows all posts on the system if the url parameter is not specified.
I would recommend using pagination like kaminari for example.

Rails need advice on controller practices

I try to follow RESTfull approach in controllers, but sometimes I need action which won't fit into default ones. E.g. I have an index action rendering all articles which is
def index
#articles = Article.all
end
but what if I also want search them? should I create separate actions for it, or should I bloat existing controller, like:
def index
if params[:search]
#articles = Article.where(id: params[:search_id])
else
#articles = Article.all
end
end
What is the right way?
You should use same action and create a index action only. And search logic goes to Article model.
You should follow this
I would keep it in index. If you want to keep it clean and standardise it you could change the controller code thus:
def index
#articles = Article.search(params[:search])
end
and then add a class method in Article
class << self
def search(options={})
if options.blank?
return self.all
else
..other search logic
end
end
end
Note that the example you gave for a search doesn't really make sense as it's specifying the id of the article. If you already know the id then that's not really a search: you might as well just go to the show page for the article.

Devise: How to prevent users from seeing other users information

When a user is logged in he has the ability to see which events he is attending. The action in the Users Controllers is the following
class UsersController < ApplicationController
before_filter :authenticate_user!
def events
#title = "Events"
#event = User.find(params[:id])
#events = #event.event_presence.paginate(page: params[:page])
render 'show_events'
end
end
However the User(2) is able to see the events of User(3) just by changing the http adress from:
/users/2/events to users/3/events
So my question is, how can I make sure that the User(2) is only able to see the events of User(2) and not of User(3).
Thanks
Filter on the current_user.id in your events method instead of params[:id]
#event = User.find(current_user.id)
However, an even better way would be to have a special route that doesn't include the id
get 'events' => 'users#events', as: :users_events
and use it like so
= link_to 'Events', users_events_path
Cancan is a gem that helps you define what users can and cannot do.
https://github.com/ryanb/cancan
It works well with devise, so don't worry about implementation.
You can also try The role:
https://github.com/the-teacher/the_role
Personally, I think its documentation is better.

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.

Resources