I'm trying to implement a Follower system similar to Twitter in my app. I'm implementing the Follower system explained in Michael Hartl's Rails Tutorial:
http://ruby.railstutorial.org/chapters/following-users
I have noticed an issue after completing the example:
Going to /users/(id)/following or /users/(id)/followers displays the following error messages:
Unknown action
The action 'following' could not be found for UsersController
or
Unknown action
The action 'followers' could not be found for UsersController
What puzzles me about these errors is that I did define these actions in the UsersController:
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.followed_users.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
Here is my routes.rb file just in case the issue lies there:
AppName::Application.routes.draw do
#get "users/index"
#get "users/show"
authenticated :user do
root :to => 'home#index'
end
root :to => "home#index"
devise_for :users
resources :users do
member do
get :following, :followers
end
end
resources :works
resources :relationships, only: [:create, :destroy]
end
Additional information: I'm using Devise to handle user authentication.
FIXED:
The issue was the placement of the following and followed actions in the Users Controller.
In your config/routes.rb, the member actions must be one per line:
resources :users do
member do
get :following
get :followers
end
end
See also http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
resources :users do
member do
get :following
get :followers
end
end
the above routes work, and provide following_user_path and followers_user_path routes respectively
Related
I am trying to create a log out for my app and in my controller this piece of code is not working. Any solution would be helpful.
def destroy
#user = user.find(params[:id])
#user.destroy
end
This is my destroy method in my sessions controller(the fact that i am destroying my user in my sessions controller might be the problem)
class UsersController < ApplicationController
def new
end
def create
#user = User.create(password: params[:password],
email: params[:email],
firstname: params[:firstname],
lastname: params[:lastname])
redirect_to user_path(#user)
end
def show
#user = User.find(params[:id])
end
end
routes.rb:
Rails.application.routes.draw do
# Fubyonrails.org/routing.html
root :to => 'static#welcome'
resources :users, only: [:new, :create, :show]
resources :session, :only => [:new, :create, :destroy]
resources :studios
end
Routes file
uninitialized constant SessionController
is the error im getting
First, don't destroy your user. It deletes it from the database, you want them to be able to login again without creating a new user right?
You need to update your routes.rb file to be:
Rails.application.routes.draw do
# Fubyonrails.org/routing.html
root :to => 'static#welcome'
resources :users, only: [:new, :create, :show]
resources :sessions, :only => [:new, :create, :destroy] # changed to sessions
resources :studios
get 'logout' => 'sessions#destroy'
end
If you don't already, you need to define a SessionsController.
class SessionsController < ApplicationController
def destroy
# insert logic that actually logs them out, the below may already be enough
# for this purpose though
redirect_to root_url, notice: "Logged Out Successfully"
end
end
When the user clicks "Logout" it gets routed to the SessionsController#destroy action which redirects them to the root URL ('static#welcome')
There is more to it then that (i.e. the views and all that jazz) but this should be helpful enough
I am new in Ruby on Rails.
I want to run http://localhost:3000/admin/users to see users index page.
But when I run this link, it guide me to http://localhost:3000/admin/login.
Is there something wrongs with my route setting?
Rails.application.routes.draw do
get 'users/new'
get 'users/show'
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: '/letter_opener'
end
root to: 'helps#top'
# admin login
get 'admin/login', to: 'admin/login#index', as: 'admin/login'
get 'admin/logout', to: 'admin/login#logout'
post 'admin/login/login'
get 'admin', to: 'admin/projects#index', as: 'admin_top'
namespace :admin do
resources :users, only: %i(index new create)
resources :projects do
resources :project_users
resources :project_comments
end
resources :images
resources :categories
resources :campanies
end
end
users_controller.rb
class Admin::UsersController < AdminController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
#users = User.all
end
def show
end
def new
#user = User.new
end
def edit
end
#Post /admin/projects
def create
#user = User.new(user_params)
if #user.save
flash[:notice] = 'User saved successfully'
redirect_to :back
else
flash[:alert] = #user.errors
binding.pry
render :new
end
end
def update
end
def destroy
end
private
def set_user
#user = User.find(params [:id])
end
def user_params
params.require(:user).permit(:campany_id, :name, :email, :password_digest, :profile, :prefecture_id, :address)
end
end
Thank you!
Your UsersControllers is under the admin namespace, that's to say you must be logged in order to access to this.
If you want to have access without validating the user is currently logged in, then you'll have to remove the constraint or verification to the controller or to make a new controller and index method which point to /admin/users but this time without the user verification.
That's:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
...
def index
#users = User.all
end
...
end
# config/routes.rb
get '/users', to: 'users#index'
'/users' or '/admin/users' as you want to do it, but if you use the last one then any person must infer that's a restricted resource .
About routing, If I do something like this:
resources :students
resources :teachers
I will get something like:
students GET /students(.:format) students#index
...
teachers GET /teachers(.:format) teachers#index
...
Changing to:
resources :students, controller: :users
resources :teachers, controller: :users
will give me:
students GET /students(.:format) users#index
teachers GET /teachers(.:format) users#index
Note that now, both resources are using the same controller Users and the same action index. But what I need, instead of using the same index action, is the students resource to use actions prefixed by students like students_index and teachers resources prefixed by teachers like teacher_index.
In other words, I want bin/rails routes to give me the following output:
students GET /students(.:format) users#students_index
teachers GET /teachers(.:format) users#teachers_index
I know that I can do the same with:
get 'students', to: 'users#students_index'
But there is a way to do the same with resources?
I don't think there's a way to do that with resources helper. What you could do (if it's only the index action you wanna override) is add an except, like this:
resources :students, controller: :users, except: [:index]
resources :teachers, controller: :users, except: [:index]
then, as you already suggested, do the individuals index actions like that:
get 'students', to: 'users#students_index', as: :student
get 'teachers', to: 'users#teachers_index', as: :teacher
Or you could reconsider the structure of your controllers... Good luck!
There is a far better way to do this as you might have surmised - inheritance.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
delegate :singular, :plural, :param_key, to: :model_name
before_action :set_resource, only: [:show, :edit, :update, :destroy]
before_action :set_resources, only: [:index]
def initialize
#model_name = resource_class.model_name
super
end
def show
end
def index
end
def new
#resource = resource_class.new
set_resource
end
def create
#resource = resource_class.new(permitted_attributes)
if #resource.save
redirect_to #resource
else
set_resource
render :new
end
end
def edit
end
def update
if #resource.update(permitted_attributes)
redirect_to #resource
else
set_resource
render :edit
end
end
def destroy
#resource.destroy
redirect_to action: "index"
end
# ...
private
# Deduces the class of the model based on the controller name
# TeachersController would try to resolve Teacher for example.
def resource_class
#resource_class ||= controller_name.classify.constantize
end
# will set #resource as well as #teacher or #student
def set_resource
#resource ||= resource_class.find(params[:id])
instance_variable_set("##{singular}", #resource)
end
# will set #resources as well as #teachers or #students
def set_resources
#resources ||= resource_class.all
instance_variable_set("##{plural}", #resources)
end
def permitted_attributes
params.require(param_key).permit(:a, :b, :c)
end
end
# app/controllers/teachers_controller.rb
class TeachersController < UsersController
end
# app/controllers/students_controller.rb
class StudentsController < UsersController
end
# routes.rb
resources :students
resources :teachers
This lets you follow the regular Rails convention over configuration approach when it comes to naming actions and views.
The UsersController base class uses quite a bit of magic through ActiveModel::Naming both to figure out the model class and stuff like what to name the instance variables and the params keys.
I want to display all comments (total number) on 'all' page. So, not all comments for a specific Post, but all comments in the entire app. I've tried with Comment.all, but it says it can't find post without an ID...
.../comments/all
routes
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
put "dislike", to: "comments#downvote"
end
end
end
comments_controller
def all
?
end
def index
#post = Post.find(params[:post_id])
#comments = #auto.comments.order("cached_votes_score DESC")
end
def show
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
end
...
You need a not nested route to comments:
routes
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
put "dislike", to: "comments#downvote"
end
end
end
get "comments#all"
comments_controller
def all
#comments=Comment.all
end
The problem is in your PostsController because what you want is not actually directly relevant to a post.
Try adding a collection route for comment
resources :comments do
member do
..
end
collection do
get :all # actually that is index
end
end
or simpler
#config/routes.rb
resources :comments, only: :index
and then a
#app/controllers/comments_controller.rb
def index
Comment.all
end
on your CommentsController will do.
In Rails, when I need:
/comments
and
/posts/1/comments
How do I best organize CommentsController? E.g. let the routes share the index actions, or work with 2 controllers?
You can work with only one controller.
I would go with a before_filter to check if the post_id param is present:
class CommentsController < ApplicationController
before_filter :find_post, only: [:index]
def index
if #post.present?
## Some stuff
else
## Other stuff
end
end
private
def find_post
#post = Post.find(params[:post_id]) unless params[:post_id].nil?
end
end
And have in your routes (with the constraints of your choice) :
resources :posts do
resources :comments
end
resources :comments
I believe you want /comments only for show and index actions, right? Otherwise the post params will be lost when creating or updating a comment.
In your routes.rb you can have something like:
resources : posts do
resources :comments
end
resources :comments, :only => [:index, :show]
In your form:
form_for([#post, #comment]) do |f|
And in your controller, make sure you find the post before dealing with the comments (for new, edit, create and update, such as:
#post = Post.find(params[:post_id])
#comment = #post...
You can do almost anything you want with rails routes.
routes.rb
match 'posts/:id/comments', :controller => 'posts', :action => 'comments'}
resources :posts do
member do
get "comments"
end
end