Removing an associated object with a link_to to the update action - ruby-on-rails

class Question < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions, allow_destroy: true
end
CategoriesController:
private
def category_params
params.require(:category).permit(:title, questions_attributes: [:id, :category_id, :title, :_destroy])
end
In the view I have a category displaying all it's posts (CategoriesController#show).
Each post is deletable.
How could I construct a link_to helper that deletes a post by updating the category?

link_to "Remove question", category_path(category, question_attributes(id: question.id, _destroy: true)), method: :put

= link_to 'Delete question', question, method: :delete, data: {confirm: 'Are you sure?'}
and in routes.rb
resources :questions
and in your controller do what's necessary
class QuestionController < ApplicationController
def delete
question = Question.find(params[:id])
question.category = nil #or whatever you need to do
question.save
end
end

link_to "Remove question", category_path(category: { questions_attributes: {id: question.id, _destroy: true}}), method: :put

This will be best done using the .delete collection method in your controller:
#app/controllers/categories_controller.rb
def delete_question
#category = Category.find params[:category_id]
question = category.questions.find params[:id]
#category.questions.delete question
end
To get this working, you'll need to use the routes and link_to helper like this:
#config/routes.rb
resources :categories do
patch "question/:id", to: "categories#delete_question" #-> domain.com/categories/15/question/1
end
#app/views/categories/show.html.erb
<% #category.questions.each do |question| %>
<%= link_to "Remove Question", category_question_removal_path(category, question) method: :patch %>
<% end %>

Related

Pathing issues in rails form_with for associated model

I am working on a project for which I have to analyse data inside a file, which the user should be able to upload. I have a model InputFile and chose to implement an analysis_facade for my Analysis model which bundles most of the functionality.
Here are the models
#app/models/analysis.rb
class Analysis < ApplicationRecord
has_one :input_file, dependent: :destroy
end
#app/models/input_file.rb
class InputFile < ApplicationRecord
belongs_to :analysis
has_many :access_data, dependent: :destroy
def uploaded_file=(file_field)
self.name = File.basename(file_field.original_filename).gsub(/[^\w._-]/,'')
self.content_type = file_field.content_type.chomp
self.data = file_field.read
end
end
Here comes my AnalysisController and the corresponding facade pattern
#app/controllers/analyses_controller.rb
class AnalysesController < ApplicationController
before_action :set_analysis, only: :show
def show
#analysis_facade = AnalysisFacade.new(#analysis)
end
private
def set_analysis
#analysis ||= Analysis.create
end
end
#app/facades/analysis_facade.rb
class AnalysisFacade
attr_reader :analysis
def initialize(analysis)
#analysis = analysis
end
def new_input_file
#input_file = analysis.build_input_file
end
def input_file
#input_file ||= new_input_file
end
def access_data
#access_data ||= analysis.input_file.access_data
end
end
My controller for InputFiles
#app/controllers/input_files_controller.rb
class InputFilesController < ApplicationController
include LogFileScanner
before_action :set_analysis, only: [:new, :create, :update]
def new
#input_file = Input_file.new
end
def create
#input_file = #analysis.build_input_file(input_file_params)
if #input_file.save
access_data = scan(#input_file.data)
#input_file.access_data.create(access_data)
end
end
def update
#input_file = #analysis.input_file
if #input_file.update
access_data = scan(#input_file.data)
#input_file.access_data.update(access_data)
end
end
private
def input_file_params
params.require(:input_file).permit(:uploaded_file, :analysis_id)
end
def set_analysis
#analysis = Analysis.find(params[:input_file][:analysis_id])
end
end
For the file upload I have these views
#app/views/analyses/show.html.erb
<%= render template: 'input_files/get' %>
#app/views/input_files/get.html.erb
<%= form_with model: [#analysis_facade.analysis, #analysis_facade.input_file], action: :update, html: {multipart: true, class: 'form-horizontal center'} do |form| %>
<div class='form-group.new'>
<% if false %>
<%= form.hidden_field :analysis_id, value: #analysis_facade.analysis[:id] %>
<% end %>
<%= form.file_field 'uploaded_file' %>
<%= form.submit "Upload", class: 'btn btn-default btn-primary' %>
</div>
<% end %>
Executing this results in error
undefined method `analysis_input_files_path' for #<#<Class:0x00007f1208040288>:0x00007f11f0d71d48>
Did you mean? analysis_path
for this loc
<%= form_with model: [#analysis_facade.analysis, #analysis_facade.input_file], action: :update, html: {multipart: true,
class: 'form-horizontal center'} do |form| %>
Why is that? I need to pass the analysis_id to params in order to create the new InputFile. But I want it to be cleaner than manually adding a hidden_field, since model_with should provide this feature. What do I miss here?
Also: Why does rails search for `analysis_input_files_path'? Its clearly a has_one relation, so shouldn't it look for analysis_input_file_path then?
EDIT:
#bin/rails routes
Prefix Verb URI Pattern Controller#Action
analyses GET /analyses(.:format) analyses#index
POST /analyses(.:format) analyses#create
new_analysis GET /analyses/new(.:format) analyses#new
edit_analysis GET /analyses/:id/edit(.:format) analyses#edit
analysis GET /analyses/:id(.:format) analyses#show
PATCH /analyses/:id(.:format) analyses#update
PUT /analyses/:id(.:format) analyses#update
DELETE /analyses/:id(.:format) analyses#destroy
input_file_access_data GET /input_files/:input_file_id/access_data(.:format) access_data#index
POST /input_files/:input_file_id/access_data(.:format) access_data#create
new_input_file_access_datum GET /input_files/:input_file_id/access_data/new(.:format) access_data#new
edit_access_datum GET /access_data/:id/edit(.:format) access_data#edit
access_datum GET /access_data/:id(.:format) access_data#show
PATCH /access_data/:id(.:format) access_data#update
PUT /access_data/:id(.:format) access_data#update
DELETE /access_data/:id(.:format) access_data#destroy
input_files GET /input_files(.:format) input_files#index
POST /input_files(.:format) input_files#create
new_input_file GET /input_files/new(.:format) input_files#new
edit_input_file GET /input_files/:id/edit(.:format) input_files#edit
input_file GET /input_files/:id(.:format) input_files#show
PATCH /input_files/:id(.:format) input_files#update
PUT /input_files/:id(.:format) input_files#update
DELETE /input_files/:id(.:format) input_files#destroy
analyses_show GET /
I was a conceptional mistake. I changed the facade model to
class AnalysisFacade
attr_reader :analysis
def initialize(analysis)
#analysis = analysis
end
def new_input_file
#analysis.build_input_file
end
def input_file
#analysis.input_file ||= new_input_file
end
def access_data
#analysis.input_file.access_data
end
end
And the view works now like this
<%= form_with model: #analysis_facade.input_file, html: {multipart: true, class: 'form-horizontal center'} do |form| %>
<div class='form-group.new'>
<%= form.file_field 'uploaded_file' %>
<%= form.submit "Upload", class: 'btn btn-default btn-primary' %>
</div>
<% end %>

How to allow users delete their own comments and not others

In my Rails App, I have a comment model ,a devise user model and a tales model. For each tales post , I have comments posted by signed-in users.The problem here is every other logged in user is able to delete the comments of other user.I want a functionality such that only the user who created the comments can delete it.
My user.rb is here
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
has_many :tales, dependent: :destroy
end
My comment.rb is here
class Comment < ActiveRecord::Base
belongs_to :tale
end
My tale.rb is here
class Tale < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
belongs_to :category
end
My routes.rb is as follows
Rails.application.routes.draw do
get 'tales/index'
devise_for :users, controllers: { registrations: "registrations" }
resources :profiles
resources :tales do
resources :comments
end
resources :categories
authenticated :user do
root "tales#index"
end
unauthenticated :user do
get "/" => "tales#index"
end
end
My comment controller is here:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#tale = Tale.find(params[:tale_id])
#comment = #tale.comments.create(comment_params)
redirect_to tale_path(#tale)
end
def destroy
#tale = Tale.find(params[:tale_id])
#comment = #tale.comments.find(params[:id])
#comment.destroy
end
private
def comment_params
params.require(:comment).permit(:name, :body, :tale_id)
end
end
The excerpt from my tales/show page to add comments is here:
<div id="comments">
<h2><%= #tale.comments.count %> Comments</h2>
<%= render #tale.comments %>
<h3>Add a comment:</h3>
<%= render "comments/form" %>
</div>
</div>
My _comment.html.erb is here
<div class="comment clearfix">
<div class="comment_content">
<p class="comment_name"><strong><%= comment.name %></strong></p>
<p class="comment_body"><%= comment.body %></p>
<p class="comment_time"><%= time_ago_in_words(comment.created_at) %>
Ago</p>
</div>
<% if user_signed_in? %>
<p><%= link_to 'Delete', [comment.tale, comment], method: :delete, data:
{ confirm: 'Are you sure?' } %></p>
<% end %>
</div>
I see no connection between user and comments and I dont the right way to do it here.Can someone guide me through this such that I can do so without using any gems .
You don't appear to have a relationship between Comment and User. You would need something like this in your Comment class assuming you are storing the user_id for each comment:
belongs_to :user
Then in your CommentsController your destroy method should be something like this:
def destroy
# Only the comments posted by that user will be returned
#comment = #user.comments.find(params[:id])
#comment.destroy
end
Add use_id in comments table if don't have
add_column :comments, :user_id, :integer
in your view file put following condition. Delete link will only visible to user who added comment.
<% if user_signed_in? && current_user.id == comment.user_id %>
<p><%= link_to 'Delete', [comment.tale, comment], method: :delete, data:
{ confirm: 'Are you sure?' } %></p>
<% end %>

Voting for nested objects using the Acts As Votable gem

I have a Restaurant model that has_many :dishes, through: dish_categories. I found a post that shows how to write the view code necessary to get things going for the Acts As Votable gem. My situation differs being that the dish model is the nested resource that's being voted upon.
I tried translating the provided code but to no avail. At this point should I create a new controller for dishes and place the votable actions there? If so how would I setup my route so I can accomplish this on my restaurant's show page?
Models
class Restaurant < ActiveRecord::Base
has_many :dish_categories, dependent: :destroy
has_many :dishes, through: :dish_categories
end
class DishCategory < ActiveRecord::Base
belongs_to :restaurant
has_many :dishes, dependent: :destroy
delegate :name, to: :dish_category, prefix: "category"
delegate :restaurant, to: :dish_category
end
class Dish < ActiveRecord::Base
belongs_to :dish_category
end
Restaurants Controller
...
def upvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.liked_by current_user
redirect_to #restaurant
end
def downvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.disliked_by current_user
redirect_to #restaurant
end
...
Routes
resources :restaurants do
member do
put "upvote", to: "restaurants#upvote"
put "downvote", to: "restaurants#downvote"
end
end
Restaurants - Show View
...
<% #restaurant.dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<b><%= dish.name %></b>
<%= link_to "Upvote", like_restaurant_path(dish), method: :put %>
<%= link_to "Downvote", dislike_restaurant_path(dish), method: :put %>
</div>
<% end %>
A number of things needed to be done to get this to work. The first order of business was moving my controller action to my dishes controller. I also added two more actions: unlike and undislike for toggle functionailty.
NOTE: Logic for authenticating non-registered for users to liking/disliking dishes would still need to be written but this should help get you started.
Dishes Controller
class DishesController < ApplicationController
before_action :load_restaurant_and_dish, only: [:like, :unlike, :dislike, :undislike]
def like
#dish.liked_by current_user
redirect_to #restaurant
end
def unlike
#dish.unliked_by current_user
redirect_to #restaurant
end
def dislike
#dish.disliked_by current_user
redirect_to #restaurant
end
def undislike
#dish.undisliked_by current_user
redirect_to #restaurant
end
private
def load_restaurant_and_dish
#dish = Dish.find(params[:id])
#restaurant = #dish.restaurant
end
end
Next was configuring my routes to correspond with my restaurant and dish models:
Routes
resources :restaurants do
resources :dishes, only: [:like, :unlike, :dislike, :undislike] do
member do
put "like", to: "dishes#like"
put "unlike", to: "dishes#unlike"
put "dislike", to: "dishes#dislike"
put "undislike", to: "dishes#undislike"
end
end
end
I ended up refactoring my show view and created a few partials to reduce clutter now that there's a little bit of logic involved:
Restaurants - Show View
...
<%= render "restaurants/dish_partials/dishes" %>
...
Dishes Partial
<% #dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<span><b><%= dish.name %></b></span>
<%= render "restaurants/dish_partials/like_toggle", dish: dish %>
</div>
<% end %>
Like Toggle Partial
<% if current_user.liked? dish %>
<%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>
<% if current_user.disliked? dish %>
<%= link_to "Undislike", undislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Dislike", dislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>

Rails polymorphic posts associations and form_for in views

I've been having trouble setting up the form for a polymorphic "department" post in the department view. I followed the rails-cast tutorial for polymorphic associations here
Models:
class Course < ActiveRecord::Base
belongs_to :department, inverse_of: :courses
has_and_belongs_to_many :users, -> { uniq }
has_many :posts, as: :postable #allows polymorphic posts
end
class Department < ActiveRecord::Base
has_many :courses, inverse_of: :department
has_many :posts, as: :postable #allows polymorphic posts
has_and_belongs_to_many :users, -> {uniq}
end
class Post < ActiveRecord::Base
belongs_to :user, touch: true #updates the updated_at timestamp whenever post is saved
belongs_to :postable, polymorphic: true #http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
belongs_to :department, counter_cache: true #for counting number of posts in department
belongs_to :course, counter_cache: true
validates :department_id, :course_id, presence: true
end
config/routes
devise_for :users
devise_scope :users do
match '/users/:id', to: "users#show", via: 'get'
end
resources :departments do
resources :courses
resources :posts
end
resources :courses do
resources :posts
end
views/departments/show.html.erb
<div class="tab-pane" id="posts"><br>
<center><h3>Posts:</h3></center>
<%= render "posts/form", postable: #department %>
</div>
views/posts/_form.html.erb
<%= render "posts/wysihtml5" %>
<center><h3>Create New Post:</h3></center>
<%= form_for [#postable, Post.new] do |f| %>
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
<%= f.label :description %>
<%= f.text_area :description, :rows => 3, class: "form-control" %>
<%= f.text_area :content, :rows => 5, placeholder: 'Enter Content Here', class: "wysihtml5" %>
<span class="pull-left"><%= f.submit "Create Post", class: "btn btn-medium btn-primary" %></span>
<% end %>
controllers/post_controller.rb
class PostsController < ApplicationController
before_filter :find_postable
load_and_authorize_resource
def new
#postable = find_postable
#post = #postable.posts.new
end
def create
#postable = find_postable
#post = #postable.posts.build(post_params)
if #post.save
flash[:success] = "#{#post.title} was sucessfully created!"
redirect_to department_post_path#id: nil #redirects back to the current index action
else
render action: 'new'
end
end
def show
#post = Post.find(params[:id])
end
def index
#postable = find_postable
#posts = #postable.posts
end
...
private
def post_params
params.require(:post).permit(:title, :description, :content)
end
def find_postable #gets the type of post to create
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
controllers/departments_controller.rb
def show
id = params[:id]
#department = Department.find(id)
#course = Course.new
#course.department_id = #department
end
The error is "undefined method `posts_path' for #<#:0x0000010d1dab10>"
I think the error has something to do with the path in the form, but I don't know what. I've tried [#postable, #postable.posts.build] as well but that just gives me undefined method: PostsController.
Anybody know what's going on and how I can fix it?
#department is passed into the form partial as a local variable, but the form calls an instance variable:
# views/departments/show.html.erb
<%= render "posts/form", postable: #department %> # <------ postable
# views/posts/_form.html.erb
<%= form_for [#postable, Post.new] do |f| %> # <------ #postable
Thus, the namespaced route is not properly determined
[#postable, Post.new] # => departments_posts_path
[ nil , Post.new] # => posts_path
Checking your routes, posts are only accessible via nested routes. posts_path is not a valid route, it's method does not exist, and the error is correct: undefined method `posts_path'
Fix:
Set a #postable instance variable in the departments controller so that the form helper can use it:
def show
id = params[:id]
#postable, #department = Department.find(id) # <-- add #postable
#course = Course.new
#course.department_id = #department
end
Then you can simply call render in the view:
<%= render "posts/form" %>

Destroy action on nested resource

Im trying to add a destroy button on my nested resource and getting this error: No route matches [DELETE] "/users/1/2/4/5/holidays/7"
Heres the relevant parts of my view,routes, models, & controllers:
<% #user.holidays.each do |h| %>
<td><%= h.name %></td>
<td><%= h.date %></td>
<td>
<%= button_to('Destroy', user_holiday_path(#user.holidays), :method => 'delete', :class => 'btn btn-large btn-primary') %>
</td>
<% end %>
Routes
resources :users do
resources :interests
resources :holidays
end
Models
class User < ActiveRecord::Base
has_many :holidays, :through => :user_holidays
end
class UserHoliday < ActiveRecord::Base
attr_accessible :holiday_id, :user_id
belongs_to :user
belongs_to :holiday
end
class Holiday < ActiveRecord::Base
attr_accessible :name, :date
has_many :user_holidays
has_many :users, :through => :user_holidays
end
Controller
class HolidaysController < ApplicationController
def index
#user_holidays = Holiday.find(params[:user_id])
#holidays = #user_holidays.holidays
end
def new
end
def show
#holiday = Holiday.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #holiday }
end
end
def destroy
#holiday = Holiday.find(params[:id])
#holiday.destroy
end
end
Thanks!!!
Change this :
<%= button_to('Destroy', user_holiday_path(#user.holidays), :method => 'delete', :class => 'btn btn-large btn-primary') %>
to this:
<%= button_to('Destroy', user_holiday_path(h), :method => 'delete', :class => 'btn btn-large btn-primary') %>
Update
Change your destroy action from :
#holiday = Holiday.find(params[:id]) to
#user_holiday = UserHoliday.find(params[:id])
and in your view:
change
<% #user.holidays.each do |h| %>
to
<% #user.user_holidays.each do |h| %>
Your associations need some correction and should be as follows:
user has_many user_holidays
user_holiday has_one holiday
user_holidays belongs_to user
You can access name and holiday via your h object:
h.holiday.name
h.holiday.date

Resources