Added an admin view to my rails app (this: http://www.interque.co/). Added a boolean state for approved to each question. I'm trying to toggle that state by clicking a link I've labeled approved.
Here is my controller:
class AdminController < ApplicationController
def index
#questions = Question.all
unless current_user && current_user.administrator?
redirect_to root_path
end
end
end
Here's the admin view I'm working with, which lists all unapproved questions:
<% if current_user %>
<% #questions.each do |question| %>
<% if question.approved == false %>
<div>
Title: <%= question.title %><br>
Description: <%= question.description %><br>
<%= link_to "Approve", question_path(question), :method => :put %>
</div>
<br>
<% end %>
<% end %>
<% end %>
Can I toggle the state by clicking a link_to? I just need to set question.approved = true.
Any help would be appreciated. Thanks in advance.
Add remote: true to your link and add a controller action that sets boolean state. You'll probably want to check that link is changed to 'approved' after that (included in the answer). Another good thing will be displaying an error message in case of error and disabling link during request.
config/routes.rb
resources :questions do
member do
put :approve
end
end
views/admin/index.html.erb
<span class="approve-question">
<%= link_to "Approve", approve_question_path(question), :method => :put, remote: true %>
</span>
views/questions/approve.js.erb
$('.approve-question').html('Approved');
controllers/questions_controller.rb
respond_to :js
def approve
#question = Question.find(params[:id])
if #question.update(approved: true)
render
else
render #question
end
end
Related
I have a rails question. I'm building a site where posts have likes, both posts and likes are their own model. A user can only like a post once, and once they like it the like button becomes an "unlike" button, that deletes the like.
I'm trying to create an experience in which the user can like, or unlike a post - and will not be redirected, but the like will update. With my limited rails knowledge, this isn't an easy task. Can anyone point me in the right direction?
Here is my /likes/_likes.html.erb template partial with the like/unlike button:
<% liked = #post.likes.find { |like| like.user_id == current_user.id} %>
<div class="likes">
<% if liked %>
<%= button_to 'Unlike', post_like_path(#post, liked), method: :delete %>
<% else %>
<%= button_to 'Like', post_likes_path(#post), method: :post %>
<% end %>
<%= #post.likes.count %><%= (#post.likes.count) == 1 ? 'Like' : 'Likes'%>
</div>
Here is my Like controller:
class LikesController < ApplicationController
before_action :find_post
before_action :find_like, only: [:destroy]
def create
if (!already_liked?)
#post.likes.create(user_id: current_user.id)
end
end
def destroy
if (already_liked?)
#like.destroy
end
end
private
def already_liked?
Like.where(user_id: current_user.id, post_id:
params[:post_id]).exists?
end
def find_post
#post = Post.find(params[:post_id])
end
def find_like
#like = #post.likes.find(params[:id])
end
end
Here is one of the views in which the _likes partial shows up (although the issue persists everywhere it appears):
<div class="post-display">
<% if #post.title %>
<h1><%= #post.title %></h1>
<% end %>
<% if #post.user %>
Post by <%= #post.user.email %>
<% end %>
<% if #post.price %>
<p>$<%= sprintf "%.2f", #post.price %></p>
<% end %>
<% if #post.description %>
<p><%= #post.description %></p>
<% end %>
<% if #post.image.present? %>
<%= image_tag #post.image.variant(:small) %>
<% end %>
<%= render 'likes/likes' %>
</div>
<% if current_user == #post.user %>
<%= link_to "Edit", edit_post_path(#post) %>
<%= button_to "Delete", #post, method: :delete %>
<% end %>
<% if #post.comments.count > 0 %>
<div class="post-comments">
<h2 class="post-comments-headline">Comments</h2>
<%= render #post.comments %>
</div>
<% end %>
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
If you don't have an answer to my question, but have an idea on how to improve my code - let me know either way! I'm trying to learn here...
Thank you,
Jill
Since you're using rails 7, rendering turbo_stream in response to "like" and "unlike" buttons will update the page without refreshing.
# config/routes.rb
resources :posts do
# NOTE: i've used singular `resource`, since there is no need to have `id`
# for the like.
resource :like, only: [:destroy, :create]
end
https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resource
# app/models/post.rb
class Post < ApplicationRecord
has_many :likes
def liked_by? user
likes.where(user: user).exists?
end
end
# app/models/like.rb
class Like < ApplicationRecord
belongs_to :post
belongs_to :user
# NOTE: avoid double likes.
validates_uniqueness_of :user, scope: :post, message: "already liked this post"
# TODO: create a unique index migration, to really make sure no double likes.
# `add_index :likes, [:post_id, :user_id], unique: true`
end
I've simplified LikesController a bit. No need for before_action filters:
# app/controllers/likes_controller.rb
class LikesController < ApplicationController
# POST /posts/:post_id/like
def create
# NOTE: uniqueness validation in `Like` model will prevent creating dup likes.
post.likes.create(user: current_user)
# you can access `like` error if you want to show it:
# like = post.likes.create(user: current_user)
# like.errors.full_messages
# NOTE: that's it, now we render `likes` partial inside a `turbo_stream`
render turbo_stream: turbo_stream.replace(
helpers.dom_id(post, :like), # this is the target div `id` that will be replaced
partial: "posts/likes", # with `likes` partial.
locals: { post: post }
)
end
# DELETE /posts/:post_id/like
def destroy
# NOTE: this will work regardless if there are any likes or not.
post.likes.where(user: current_user).destroy_all
# NOTE: alternatively, we can render the same `turbo_stream` as above
# in a template `likes/likes.turbo_stream.erb`:
render :likes
end
private
def post
#post ||= Post.find params[:post_id]
end
end
<!-- app/views/posts/_likes.html.erb -->
<!-- `dom_id` helps us generate a uniq id: "like_post_1" -->
<div id="<%= dom_id(post, :like) %>">
<!-- yes, there is a rails helper for this -->
<%= pluralize post.likes.count, "like" %>
<% if post.liked_by? current_user %>
<%= button_to "Unlike", post_like_path(post), method: :delete %>
<% else %>
<%= button_to "Like", post_like_path(post) %>
<% end %>
</div>
This turbo_stream is the same as in create action:
<!-- app/views/likes/likes.turbo_stream.erb -->
<%= turbo_stream.replace dom_id(#post, :like) do %>
<%= render "posts/likes", post: #post %>
<% end %>
https://turbo.hotwired.dev/handbook/streams
Try this
views file where likes partial render
<div id='post_likes'>
<%= render 'likes/likes' %>
</div>
/likes/_likes.html.erb
<div class="likes">
<% if liked %>
<%= button_to 'Unlike', post_like_path(#post, liked), method: :delete, remote: true %>
<% else %>
<%= button_to 'Like', post_likes_path(#post), method: :post, remote: true %>
<% end %>
<%= #post.likes.count %><%= pluralize(#post.likes.count, 'Like') %>
</div>
views/likes/create.js.erb
$('#post_likes').html('<%= render 'likes/likes' %>');
views/likes/destroy.js.erb
$('#post_likes').html('<%= render 'likes/likes' %>');
In my customer controller the update method code is like bellow:
def update
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
In my view in customers index page I am planning to add a "link_to" link, if it is clicked, then that particular customers field "doc_delete" should be updated with value "TRUE".
<td><%= link_to "[Update", *************what is here ?******** method: :put %></td>
You can pass hidden params through button_to:
<%= button_to "Update", user, method: :put, params: { doc_delete: true } %>
This will create a micro-form, much like what Marwen alluded to. Whilst quite inefficient, it will be the best way to send data to your update action.
--
Another, more efficient, way would be to define a custom route/action:
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/users/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
redirect_to customers_path if #customer.update doc_delete: true
end
end
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<%= link_to "Update", customer_doc_delete_path(customer) %>
<% end %>
You will need a form to do that for you
<% unless customer.doc_delete? %>
<%= form_for customer do |f| %>
<%= f.hidden_field_tag :doc_delete, true %>
<%= f.submit %>
<% end %>
<% end %>
Where to insert this form?
Well if you are rendering you costumers using:
<%=render #costumers %>
then you will add the form in the /customers/_customer.html.erb
If you are looping them manually:
<% #customers.each do |customer| %>
<%=customer.full_name %>
## Here you can add the form
<% end %>
An another way, you can use Ajax.
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<% if !customer.doc_delete == true %>
<%= link_to "Update", customer_doc_delete_path(customer), remote: true %>
<% else %>
<%= Updated %>
<% end %>
<% end %>
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/customers/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
if #customer.update doc_delete: true
respond_to do | format |
format.js {render :nothing => true}
end
end
end
end
In my index.html
<td>
<%= hidden_field_tag 'delete_present', :value => "present" %>
<%=link_to "[update]", customer_path(customer, :doc_delete => true), :method => :put, :confirm => "Are you sure?" %>
</td>
In my customer controller
def update
if params[:doc_delete].present?
#customer= Customer.find(params[:id])
#customer.doc_delete=true
#customer.save
redirect_to customers_path
else
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
end
I'm doing a Rails tutorial, and trying to figure out why this is happening.
I'm making a to-do list, and everytime I try and insert a record into my Todo model, I get the following:
Here is the new.html.erb view that this is from:
<h1>Add new item to your todo listM</h1>
<%= form_for #todo, :url=>todo_path(#todo) do |f| %>
<%= f.label :name %> <%= f.text_field :name%>
<%= f.hidden_field :done, :value => false %>
<%= f.submit "Add to todo list" %>
<% end %>
Here is index.html.erb from where the user is linked to new.html.erb
<h1>TASKS</h1>
<h3> TO DO </h3>
<ul>
<% #todos.each do |t| %>
<li>
<strong><%= t.name %></strong>
<small><%= link_to "Mark as Done", todo_path(t), :method => :put %></small>
</li>
<% end %>
</ul>
<h3> DONE </h3>
<ul>
<% #todones.each do |t| %>
<li>
<strong><%= t.name %></strong>
<small><%= link_to "Remove", t, :confirm => "You sure?", :method => :delete %></small>
</li>
<% end %>
</ul>
<%= link_to "Add new task", new_todo_path %>
Here is the TodoController I have managing these actions:
class TodoController < ApplicationController
def index
#todos = Todo.where(done:false)
#todones = Todo.where(done:true)
end
def new
#todo = Todo.new
end
def todo_params
params.require(:todo).permit(:name, :done)
end
def create
#todo = Todo.new(todo_params)
if #todo.save
redirect_to todo_index_path, :notice => "Your todo item was created!"
else
render "new"
end
end
def update
#todo = Todo.find(params[:id])
if #todo.update_attribute(:done, true)
redirect_to todo_index_path, :notice => "Your todo item was marked done!"
else
redirect_to todo_index_path, :notice => "Couldn't update your task"
end
end
def destroy
#todo = Todo.find(params[:id])
#todo.destroy
redirect_to todo_index_path, :notice => "Your todo item was deleted"
end
end
And finally the routes.rb
Oneday::Application.routes.draw do
devise_for :users
root 'home#index'
resources :todo
end
Any input as to why this is happening and how to rectify it would be great.
You do not comply with the rails convention. Use plural form for resources. Then, your action is correct.
TodosController, todos_controller.rb, resources :todos
( Rails use singular/plural format to support RESTful links and to recognize named actions )
This
<%= form_for #todo, :url=>todo_path(#todo) do |f| %>
will set (or leave) the form http method to get. You could change it to:
<%= form_for #todo, :url=>todo_path(#todo), method: :post do |f| %>
or even shorter, leave it to Rails to find out what method is needed:
<%= form_for #todo do |f| %>
I found a fix to this exact issue if anyone is still curious, i know its an old issue and an easy one at that, but still figured id solve it. the original route todo_path leads to todo#show. todo_index however is assigned to todo#index and todo#create so its what we want. the line should look like this:
<%= form_for #todo, :url => todo_index_url(#todo), method: :post do |f| %>
I encountered a similar issue with one of my applications and stumbled across this post as a fix. None of the suggestions worked for me, but i was able to fix it with a little tinkering on the routes.
In my rails app I have a simple poll system. User checks options he likes and votes. The problem is check_boxes not showing and even if will showing I wouldn't Vote all the same because made some mistakes in my code likely
polls/show.html.haml:
- for question in #poll.questions.each
%span= question.title
%p= question.comment
- form_tag upvote_question_path do
%p= check_box_tag question, question.id
%p= question.title
= submit_tag 'Vote'
questions_controller.rb
class QuestionsController < ApplicationController
def upvote
#question = Question.find(params[:id])
#question.increment!(:votes_count)
redirect_to :back
end
end
If you need more information please comment!
Try this
<% #poll.questions.each do |question| %>
<span> <%= question.title %></span>
<%= form_tag upvote_question_path do %>
<p><%= check_box_tag :question_id, question.id %></p>
<p><%= question.title %></p>
<%= submit_tag 'Vote' %>
<% end %>
<% end %>
In controller
def upvote
#question = Question.find(params[:question_id])
#question.increment!(:votes_count)
redirect_to :back
end
Several things:
Your update_vote_path doesn't seem to be a member route?
You could easily achieve this with a button
You're looping through questions -- maybe this is empty?
Code
I would try this:
#config/routes.rb
resources :questions do
#/questions/2/vote
post :vote
end
#app/views/your_view
- for question in #poll.questions.each
%span= question.title
%p= question.comment
- button_to "Vote", vote_questions_path(question), method: :post
#app/controllers/questions_controller.rb
class QuestionsController < ApplicationController
def vote
#question = Question.find(params[:question_id])
#question.increment!(:votes_count)
redirect_to :back
end
end
Modularity
You're trying to manually submit a form with specific params, whereas you can handle all of that with the routes
I think your main issue will be how you're calling the #poll.questions variable. I don't believe it will be loading correctly, but without seeing your other code, this is just speculation
I have a controller that has the following index action :
def index
if request.post?
flash[:notice] = "Searching.."
redirect_to songs_path # where songs_path is this index page that i am on
end
end
in my application layout i have defined the flash section as such
<% if flash[:notice] %>
<div id='notice'><%= flash[:notice] %></div>
<% end %>
and on my pages_path i've got
<% form_for :search do |f| %>
<%= f.label :search_text %>
<%= f.text_field :search_text %>
<p>
<%= f.submit "Search" %>
</p>
<% end %>
The final result should be a search through a youtube api ( youtube-g) but now i would only wish to make that flash notification appear when i click on the "Search" button , and it doesn't .. any ideas why ?
index action ordinary is GET request. So, if in your routes.rb there is something like resources :searches then your code won't work.
try this:
def create
render :text => "Searching.."
end
because POST /searches will refer to create action
you should update your RESTful route to like this
resources :songs do
collection do
get 'search'
end
end
Then in your Controller
def search
# Perform Your search here like Youtube ..
flash[:notice] = "Searching.."
redirect_to songs_path # This should work now.
end