Undefined method link_to_edit using Draper decorator - ruby-on-rails

I've got a User and Post models, which are related to each other in a classical way -- User has_many :posts and Post belongs_to :user. In my users#show, where I display a user's profile, I also have a list of all posts he has made. Also, I wanted to have links to edit and delete each post respectfully. So, I made up with this:
<% #user.posts.each do |post| %>
<h1><%= link_to post.title, post_path(post) %></h1>
<% if #user == current_user %>
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Delete', post_path(post), method: :delete %>
<% end %>
<% end %>
But surely placing this logic into view results in a mess, so I decided to use Draper and write decorators for that. As we are going to check rights for posts#edit and posts#delete methods, I came up with a decorator for Post model and tried to use it in PostsController. Here it goes:
class PostDecorator << Draper::Decorator
delegate_all
def link_to_edit
if object.user == current_user
h.link_to 'Edit', h.edit_post_path(object)
end
end
def link_to_delete
if object.user == current.user
h.link_to 'Delete', h.post_path(object), method: :delete
end
end
end
Then, in my PostsController:
# ... class definition
before_action :set_post, only: [:show, :edit, :update, :destroy]
# ... other controller methods
def edit; end
def update
if #post.update(post_params)
#post.save
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to feed_path
end
private
# Using FriendlyId gem to have neat slugs
def set_post
#post = Post.friendly.find(params[:id]).decorate
end
But every time I try to render my User profile with list of his posts, with the use of my new helpers <%= post.link_to_delete %> and <%= post.link_to_edit %> instead of that conditional mess, it just returns me the following error:
What am I doing wrong?

You probably figured this out in the meantime but here's an answer for others: You were calling #post = ....decorate in your controller but you are using #user.posts.each { |post| ... } in your view. The objects fed to that block are not decorated. Only #post is.
In your view you should have done something like #user.posts.each { |raw_post| post = raw_post.decorate } and so on. Obviously, with ERB syntax. Or #user.decorated_posts.each ... where
class User < ActiveRecord::Base
...
def decorated_posts
# this will load every post associated with the user.
# if there are a lot of them you might want to only load a limited scope of them
posts.map(&:decorate)
end
...
end

Related

How can show the data related to logged in user in rails

I am trying display the task related to logged in user but on my html page nothing show except the tag data
task_controller.rb
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(#current_user.id)
render template: 'task/allTask'
end
end
end
routes.rb
get 'all_task' => 'task#all_task'
task.erb
<p>All Task</p>
<% if user_signed_in? %>
<%#all_task.daily_task %>
<%#all_task.date %>
<%#all_task.created_at %>
<%end %>
Start by setting up an assocation between users and tasks:
class User < ApplicationRecord
# ...
has_many :tasks
end
Then setup the route and controller:
get '/user/tasks', to: 'users/tasks#index', as: :user_tasks
# app/controllers/users/tasks_controller.rb
module Users
class TasksController < ApplicationRecord
before_action :authenticate_user!
# display all the tasks belonging to the currently signed in user
# GET /user/tasks
def index
#tasks = current_user.tasks
end
private
# You don't need this if your using Devise
def authenticate_user!
unless current_user
redirect_to '/path/to/your/login',
notice: 'Please sign in before continuing'
end
end
end
end
Note that when you have a route like this that displays resources that belong to the current user you should use a callback to bail early and redirect the user to sign in instead of using if current_user.present? and giving a response which is meaningless to the user. This code should be DRY:ed into your ApplicationController (even better yet is to not reinvent the auth wheel).
You can link to the users tasks with:
<% if current_user.present? %>
<%= link_to 'My tasks', user_tasks_path %>
<% end %>
In your view you need to iterate across the returned tasks:
# app/views/users/tasks/index.html.erb
<p>All Tasks</p>
<% if #tasks.any? %>
<% #tasks.each do |task| %>
<%= task.daily_task %>
<%= task.date %>
<%= task.created_at %>
<% end %>
<% else %>
<p>You don't have any tasks.</p>
<% end %>
You can cut duplication here by using partials.
Can you make sure if the instance variable #current_user is defined? If not, try the following:
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(current_user.id)
render template: 'task/allTask'
end
end
end
instead of
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(#current_user.id)
render template: 'task/allTask'
end
end
end

Rails: Edit Comment without redirecting to the Edit View

I'm building simple web app where people can share their thought / pictures / so on. It has just two controllers Post, and nested in it Comments. Generally, at the moment all works perfectly, users can add, edit and delete posts the same way as comments. The thing I'm trying to do, and have big troubles with, is possibility to Edit comments without redirecting to the Edit Comment View - so to be able to do it from the "posts#show" level, the same way as comments actually are being created. I think it just would look much more nicer... Here are my:
posts_controller.rb
class PostsController < ApplicationController
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all.order(created_at: :desc)
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#comments = #post.comments
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to root_path
end
private
def find_post
#post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :description)
end
end
comments_controller.rb
class CommentsController < ApplicationController
before_action :find_post, only: [:create, :edit, :update, :destroy]
def create
#comment = #post.comments.create(comment_params)
if #comment.save
redirect_to #post
else
render 'new'
end
end
def edit
#comment = #post.comments.find(params[:id])
end
def update
#comment = #post.comments.find(params[:id])
#comment.update_attributes(comment_params)
if #comment.save
redirect_to #post
else
render 'edit'
end
end
def destroy
#comment = #post.comments.find(params[:id])
#comment.destroy
redirect_to #post
end
private
def find_post
#post = Post.find(params[:post_id])
end
def comment_params
params.require(:comment).permit(:content)
end
end
View show.html.haml - for post
%h1 Show page
%h3= #post.title
%p= #post.description
=link_to "Edit memory", edit_post_path
=link_to "Delete memory", post_path, method: :delete
%h4 Share your thoughts about the memory
- #comments.each do |comment|
%p= comment.content
=link_to "Edit thought", edit_post_comment_path(#post, comment)
=link_to "Delete thought", post_comment_path(#post, comment), method: :delete
= simple_form_for([#post, #post.comments.build]) do |c|
= c.input :content
= c.submit "Share thought"
View edit.html.haml - for comments (the one I'd like to get rid of / nest somehow to the view shown above)
= simple_form_for([#post, #comment]) do |c|
= c.input :content
= c.submit "Update thought"
I believe there is a simple solution, however despite the fact that I've been reading a lot about possible solution, for a newbie like me it's still difficult to figure out how this should be programed.
The terminology you're looking for is in-place editing, or inline editing
You'll either need a form or JQuery plugin to make any comment written by current_user (assuming you're using Devise) editable.
I've done this a little (you can sign up for free here, click "profile" and then edit the description):
--
The way you'd want to do it is something like this:
#app/assets/javascripts/application.js
# include x-editable scripts
$(".editable).editable([..options..]);
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= post.body %>
<% post.comments.each do |comment| %>
<% if comment.author == current_user %>
<%= content_tag :div, comment.body, class: "editable" %>
<% end %>
<% end %>
<% end %>
I've forgotten how we implemented the provided example; x-editable seems to be the rage now.
Here's how it works:
Include X-Editable (or other plugin) in your app
X-Editable will check for the presence of a .class or #id on an element
Providing such an element will allow X-Editable to make it editable
X-Editable then sends the completed request to the server, acting as Ajax
Looks like there's an x-editable-rails gem you can use:
#Gemfile
gem `x-editable-rails`
#app/assets/javascripts/application.js
#= require editable/bootstrap-editable
#= require editable/rails
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<% post.comments.each do |comment| %>
<%= editable comment, :body if comment.author == current_user %>
<% end %>
<% end %>

Rails Ancestry Gem cannot access child/parents

I've been trying to get the ancestry gem for rails to work but I seem to fail at implementing even the most simplest case. I'm new to rails so I could easily be making a rookie mistake.
What I was trying to implement was just a normal commenting system where replies to comments would be shown all on the same page using ancestry's child/parent functionality.
Below is my show html which has a reply link which is where I am trying to set up the parent child relationship:
<p id="notice"><%= notice %></p>
<%=#comment.children.nil?%>
<p>
<strong>Body:</strong>
<%= #comment.body %>
</p>
<p>
<%if !#children.nil?%>
<% #children.each do |c|%>
c.body
<%end%>
<%end%>
</p>
<p>
<strong>Author:</strong>
<%= #comment.author %>
</p>
<%= link_to 'Reply', new_comment_path, :parent_id => #comment %>
<%= link_to 'Edit', edit_comment_path(#comment) %> |
<%= link_to 'Back', comments_path %>
And here is some of the controller methods:
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# GET /comments
# GET /comments.json
def index
#comments = Comment.all
end
# GET /comments/1
# GET /comments/1.json
def show
#children=#comment.children
end
# GET /comments/new
def new
#parent_id = params.delete(:parent_id)
#comment = Comment.new(:parent_id => #parent_id)
end
//omitted code
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
I can not seem to get any parent/child relationship with the above code
ok I figured it out in the end. I was only passing the parent parameter to the "new" controller method, I should have been passing it again after this at the create stage. Was a silly mistake.

Rails User Association with comments

I have a User scaffold (DEVISE), a Comments scaffold and a movies scaffold
Currently, Comments are posted on the movie show page.
What i'm having trouble with is having the comment be created by User. So that a comment is create by a User.
So if i display the comment in the movies/:show
I could do
Body: <%= comment.body %>
Author: <%= comment.user.first_name %>
How would I make a comment belong to a user, and only editable and destroy-able BY that user only?
Please dont tell to use before_filter :authenticate_user!, only: [:create,:destroy]
or follow the Michael Hartl Tutorial with Microposts because I have already done both of these and they dont work
Anyways, does anyone know how I could do this?
MAny Thanks
first I would show the edit and destroy link only to owner with:
<% if comment.user == current_user %>
<%= link_to "edit", ... %>
<%= link_to "delete", ... %>
<% end %>
and then just in case for smart guys who knows how to use inspect element in chrome, I would do a controller level check for comment owner:
def edit
#comment = Comment.find(params[:id])
if #comment.user == current_user
#comment.update_attributes(....)
#message = "Comment updated or created or deleted, depends on method"
else
#message = "It's not your comment wise guy :)"
end
redirect_to :back, notice: #message
end
the same for destroy and update method.
!not a copy/paste ready code.
this is what I did once and it worked quite nice, other method you can use gem cancan https://github.com/ryanb/cancan and set abilities for users.
can :edit, Comment, :user_id => user.id
can :destroy, Comment, :user_id => user.id
with setting abilities this way only owner will be able to access edit page and the update, destroy actions.
what's about devise helper 'current_user'? something like this:
class Comment < ActiveRecord::Base
belongs_to :user
end
class CommentsController < ApplicationController
def edit
comment = current_user.comments.where(id: params[:id]).first
if comment.nil?
...
401 error or something else (current user is not creator of this comment)
else
...
end
end
end
And also you can check permissions in view:
<% if comment.user == current_user %>
<%= link_to "edit comment" ... %>
<%= link_to "delete comment" ... %>
<% end %>
To make the comment belongs to user, in your create action:
comment = current_user.comments.new(params[:comment])
To make it editable/destroyable only for owner
before_filter :find_comment, only: [:show, :edit, :update, :destroy]
before_filter :authorize_user!, only: [:edit, :update, :destroy]
#...
private
def find_comment
#comment = Comment.find params[:id]
end
def authorize_user!
deny_access if #comment.user != current_user # example
end
Making sure that a user is signed in with :authenticate_user! is a good thing, but you have to associate the comment with th user too.
Devise gives you a current_user. So if your Comment belongs_to :user and your User has_many :comments write in your CommentsController:
def new
#comment= current_user.comments.new
end
def create
#comment= current_user.comments.new(params[:comment])
if #comment.save
...
end
end
def edit
#comment= current_user.comments.find(params[:id])
end
def update
#comment= current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
...
end
end

How to allow only the author of a post edit or delete it in Ruby on Rails?

I have a user model and a question model.
In the user model:
has_many :questions
The question model:
belongs_to
in my questions/show.html.erb
<% if #question.user == current_user %>
<%= link_to 'Edit', edit_question_path(#question) %> | <%= link_to 'Destroy', #question, method: :delete, data: { confirm: 'Are you sure you want to delete this job?' } %>
<%= link_to 'Back', questions_path %>
<% else %>
<%= link_to 'Back', questions_path %>
<% end %>
How can only the user that authored the question edit and delete it?
Take a look at CanCan, the authorization gem by Ryan Bates of Railscasts. It's great for Rails authorization needs.
First, you'll create an Ability class that defines all of the abilities in the application.
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Question, user_id: user.id
end
end
Then, you'll be able to easily integrate authorization into your controllers.
class QuestionsController < ApplicationController
def update
authorize! :manage, #question
...
end
def destroy
authorize! :manage, #question
...
end
end
And also customize your views.
<% if can? :manage, #question %>
<%= link_to 'Edit', edit_question_path(#question) %> | <%= link_to 'Destroy', #question, method: :delete, data: { confirm: 'Are you sure you want to delete this job?' } %>
<% end %>
All you need in your controller is:
def destroy
#question = current_user.questions.find(params[:id])
#question.destroy
render ... #anything you want to render
end
The previous code will ensure that an user can only delete his own questions. If the id of the question doesn't belongs to the user no question will be deleted and it would throw and ActiveRecord::RecordNotFound - Internal Server error. You can add a begin - rsecue block to catch this exception an handle it as you want.
def destroy
begin
#question = current_user.questions.find(params[:id])
#question.destroy
render or redirect_to ....
rescue Exception ActiveRecord::RecordNotFound
flash[:notice] = 'not allow to delete this question'
redirect_to ....
end
end
Other simple way is to add a before filter in your controller
before_filter :require_authorization, only: [:delete]
...
def destroy
#question = current_user.questions.find(params[:id])
#question.destroy
render or redirect_to ....
#With the before filter this code is only going to be executed if the question belongs to the user
end
...
private
def require_authorization
redirect_to :root unless current_user.questions.find_by_question_id(params[:id])
#Use the find_by to avoid the ActiveRecord::RecordNotFound and get a nil instead in case the question id doesn't belong to a question of the user
end
you can try changing your if to the following:
<% if current_user.questions.include?(#question) %>
Also you can take a look at :inverse_of
Then in your Edit and Delete actions in the controller you can again check for the right user before showing the edit form or deleting the question.

Resources