Update Button Via AJAX - ruby-on-rails

I have a button that adds a vote to a post whenever a user clicks it. The posts are being rendered in the index view, but when the button is clicked for a user to add a vote, I get a 500 error in the console, but on refresh the vote has been added. Below is the code I'm using:
Pages Controller where the posts are being rendered:
#posts = current_user.feed.paginate(:per_page => 6, page: params[:page])
Favorites Controller:
def create
current_user.favorites.create(:post_id => params[:post_id])
respond_to do |format|
format.html { redirect_to post }
format.js { render :toggle }
end
end
Toggle.js.erb (favorites/toggle.js.erb)
$("#favorite").html("<%= escape_javascript render('shared/fave_form') %>");
Favorite Form
<% if current_user.favorites.find_by_post_id(post.id) %>
<% else %>
<%= form_for favorite, :html => { :method => :delete }, :remote => true do |f| %>
<%= f.submit "Unlike" %>
<% end %>
<% end %>

Related

Rails 7: Turbo stream is showing partial only

I am having a problem with the turbo-rails gem. First I installed the latest version in my Rails 7 application. On my site, I have a select input which is wrapped in a form, with a partial below that form that shows the data. Now I want to apply a filter using the select and dynamically update the data using this turbo-rails package. My form html looks like this:
<div class="users">
<div class="filters">
<%= form_with url: '/users/load', method: :get, data: { turbo_frame: :form_response } do |form| %>
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
options: #genders,
classes: 'filter',
name: 'gender',
} %>
<%= form.submit %>
<% end %>
</div>
<%= turbo_frame_tag :form_response do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
</div>
In my routes, I created this get request which is forwared to a load method in my controller like this:
get '/users' => "users#index"
get '/users/load' => "users#load"
And then in my controller I have the 2 methods written like this:
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#genders = ["Male", "Female"]
#users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
end
def load
#users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
if params[:gender]
#users = #users.where(gender: params[:gender])
end
respond_to do |format|
format.html { render partial: 'users/user_list', locals: { users: #users } }
end
end
end
The problem is that when I go to this page, select a gender and hit the submit button, I get to see the user data with the correct genders, but I only see the partial loaded, so the rest of the page is gone. I can see in the network tab of developer tools in Chrome that the request headers is set to:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
I want to use the turbo-streams instead of the turbo frames, because I need to update more of these items on the same page. Why is it not rendering the content inside the page, instead of rendering the partial only?
How can this be fixed?
To answer your question, you're rendering a partial without turbo stream or turbo frame, so you're only getting a partial as response.
I think, a few examples will explain everything.
# config/routes.rb
resources :users
# app/controllers/users_controller.rb
def index
scope = User.order(created_at: :desc)
scope = scope.where(name: params[:search]) if params[:search]
#users = scope
end
"Preserve log" is quite useful when working with turbo frame and it redirects and clears the console:
https://developer.chrome.com/docs/devtools/console/reference/#persist
Turbo FRAME using GET request with HTML response
We are in index action and the form is submitting back to index.
# app/views/users/index.html.erb
# expect :users_index turbo frame in a response vvvvvvvvvvvvvvvvvvvvvvvvv
<%= form_with url: users_path, method: :get, data: { turbo_frame: :users_index } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
# turbo frame in a response needs to match turbo frame on the current page,
# since we're rendering the same page again, we have the matching frame,
# only content inside this frame is updated.
<%= turbo_frame_tag :users_index do %>
<%= render #users %>
<% end %>
# If you render some other page, you have to wrap it in
# `turbo_frame_tag :users_index`
If you want to update the url as well, so you don't lose the search on refresh:
<%= turbo_frame_tag :users_index, data: { turbo_action: :advance } do %>
<%= render #users %>
<% end %>
Turbo STREAM using GET request with TURBO_STREAM response
You have to set data-turbo-stream="true" to send a GET stream.
# app/views/users/index.html.erb
<%= form_with url: users_path, method: :get, data: { turbo_stream: true } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render #users %>
<% end %>
Add turbo_stream format to respond to this request:
# app/views/users/index.turbo_stream.erb
# update content inside <div id="users_index">
<%= turbo_stream.update :users_index do %>
<%= render #users %>
<% end %>
# add another `turbo_stream` here if you'd like.
Turbo STREAM using POST request with TURBO_STREAM response
# config/routes.rb
resources :users do
# # add `search` action
# post :search, on: :collection
# i'll be lazy and post to :index
post :search, action: :index, on: :collection
end
POST form submissions are sent as TURBO_STREAM by default and it will render index.turbo_stream.erb.
# app/views/users/index.html.erb
<%= form_with url: search_users_path do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render #users %>
<% end %>
# app/views/users/index.turbo_stream.erb
<%= turbo_stream.update :users_index do %>
<%= render #users %>
<% end %>
Test set up
Just do a simple set up:
rails --version
# Rails 7.0.4
rails new turbo-test -j esbuild
cd turbo-test
bin/rails g scaffold User name
bin/rails db:migrate
open http://localhost:3000/users
bin/dev
# app/controllers/users_controller.rb
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
# Add this line:
format.turbo_stream { render turbo_stream: turbo_stream.prepend(:users, partial: "user", locals: { user: #user }) }
format.html { redirect_to user_url(#user), notice: "User was successfully created." }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
# app/views/users/index.html.erb
# submit form
<%= render "form", user: User.new %>
# new user gets prepended here
<div id="users">
<%= render #users %>
</div>

How to set "approved" user attribute to true with a button in Rails

I have a user model established by Devise and it has a Boolean attribute "approved" with default value set to false.
I created a separate controller called "newcomers" where I set index action to display all not approved users and update action to set "approved" to true
class NewcomersController < ApplicationController
def index
#users = User.where("approved = ?", false)
end
def update
#user = User.find(params[:id])
#user.update_attribute :approved, true
respond_to do |format|
format.html { redirect_to newcomers_path, notice: 'Member was successfully approved!' }
end
end
end
So, in my view I did
<h1>Members Requests</h1>
<% #users.each do |user| %>
<%= user.email %>
<%= form_for user, url: newcomers_path, method: :patch do |f| %>
<%= f.submit "Approve" %>
<% end %>
<% end %>
and in my routes I have resources :newcomers
So, I do not know how to make the button work the way it should. Meaning it should just update the attribute "approved" and set it to true. and return to the same page (index)
Try following code
<h1>Members Requests</h1>
<% #users.each do |user| %>
<%= user.email %>
<% if user.approved %>
<%= link_to 'Unapprove', newcomer_path(user, approve: false), method: :put %>
<% else %>
<%= link_to 'Approve', newcomer_path(user, approve: true ), method: :put %>
<% end %>
controller
def update
#user = User.find(params[:id])
#user.update_attribute :approved, params[:approve]
respond_to do |format|
format.html { redirect_to newcomers_path, notice: 'Member was successfully approved!' }
end
end
Just use a link_to in your view an point it to the update action in your controller:
<%= link_to 'Approve', newcomer_path(user_id), method: :put %>
In your controller just update the user with the given id an redirect to index path as you already do.
If you want it somewhat more fancy you can add a remote: true to your link_to and respond with an js to delete only the affected row from your view.
Make below changes
In view
<%= form_for user, url: newcomer_path(user, approved: true), method: :patch do |f| %>
<%= f.submit "Approve" %>
<% end %>
and in controller
def update
if params[:user].nil?
#user = User.find(params[:id])
#user.update_attribute :approved, true
respond_to do |format|
format.html { redirect_to newcomers_path, notice: 'Member was successfully approved!' }
end
end
end
let me know if it is not working...

Adding to database without refreshing the page?

When I add a model in my index action, the create action is invoked that adds an instance of the model to the database. This is the following create action:
tracks_controller.rb
def create
#track = Track.new(params[:track])
if #track.save
redirect_to(root_url) //Want to change this!
else
#tracks = Track.all
render :action=>"index"
end
end
Where you can see that I am redirected to my root url (where I want to be) everytime create is invoked. However, how can I carry this out without refreshing the page? Since tracks are being played, I do not want the page to be refreshed whenever something is added to the database.
If I change this line to render :action=>"index", then I receive the following error in my index.html.erb file
undefined methodeach' for nil:NilClass`
15: <p>Database is empty!</p>
16: <%else%>
17: <br>
18: <% #tracks.each do |track| %>
19: <div id="list_container">
20: <ul>
21: <li class="list_container">
How do I go about achieving this?
form
<%= form_for #track, remote: true %>
form fields
<%= f.submit %>
<% end %>
app/views/tracks/create.js.erb
<% if #track.valid? %>
  $(".tracks").prepend('<%= j(render(#track)) %>'); // make sure you have _track.html.erb
$("ID OR CLASS OF YOUR FORM")[0].reset(); // this will clear your form inputs
<% else %>
alert('Something Went Wrong');
<% end %>
app/views/tracks/index.html.erb
<div class="tracks">
<% #tracks.each do |track| %>
<%= render :partial => 'track' %>
<% end %>
</div>
app/views/tracks/_track.html.erb
some code here to show track:
<div>track.id</div>
<div>track.name</div>
index.html.erb needs the #tracks variable. Therefore, you have to set it before rendering the page.
For example:
def create
#track = Track.create(params[:track])
#tracks = Track.all
render :action => "index"
end
The simplest, quickest way to get it is to add :remote => true to you form_for!
If you are posting track from a form then make the remote to true
like
<%=form_for #track, :remote => true do |f| %>
your input fields
<%end %>
index.html.erb
<div id="track_list">
<%= render :partial => 'tracks_record' %>
</div>
_tracks_record.html.erb
<% #tracks.each do |track|%>
your code
<% end %>
in controller
def create
#track = Track.create(params[:track])
#tracks = Track.all
respond_to do |format|
format.js
end
end
Create one js.erb file for create.js.erb
<% if #track.valid? %>
('#track_list').html('<%= escape_javascript( render :partial => "tracks_record" ) %>');
<% else %>
alert('Could not save');
<% end %>
This will refresh the data and populate the new entries without refreshing the page.
Unfortunately I can't comment on Debadatt's suggestion but I found it much better than the top post. My only notes are to change this to
def create
#track = Track.create(params[:track])
#tracks = Track.all
respond_to do |format|
format.js
end
end
to this:
def create
#track = Track.create(params[:track])
#tracks = Track.all
respond_to do |format|
format.js { render layout: false, content_type: 'text/javascript' }
end
end
And this:
('#track_list').html('<%= escape_javascript( render :partial => "tracks_record" ) %>');
to this:
$('#track_list').html('<%= escape_javascript( render :partial => "tracks_record" ) %>');
Great suggestion thanks Debadatt

Rails: AJAX + destroy action in controller not working?

Hi I am currently making an app with users, albums, and photos. I managed to make the AJAX work for the create action when I upload pics, but I can't get AJAX to work for the delete. Instead, it redirects me to an empty show page for the pic. Can anyone give me some guidance?
photos controller:
def destroy
#user = User.find(params[:user_id])
#album = #user.albums.find(params[:album_id])
#photo = #album.photos.find(params[:id])
# #photo.destroy
flash[:success] = "Photo successfully deleted."
respond_to do |format|
if #photo.destroy
format.js
# format.json { render json: #photo, status: :created, location: #photo}
# redirect_to user_album_path(#user, #album)
end
end
end
destroy.js.erb (I'm supposed to have this right?)
$('.destroypicswrapper').remove(".<%= #photo.avatar.url %>");
_photodestroy.html.erb partial:
<div class="picthumb <%= photo.avatar.url %>">
<%= link_to image_tag(photo.avatar.url ? photo.avatar.url(:small) : #album.name), root_url %>
<br>
<%= link_to "Delete", user_album_photo_path(#user, #album, photo), :remote => true, method: :delete, data: { confirm: "Are you sure? "} %>
</div>
and finally the albums/edit.html.erb page where the delete is initially happening:
<% provide(:title, "Edit album") %>
<%= render 'form' %>
<div class="destroypicswrapper">
<% if #album.photos.any? %>
<%= render :partial => 'photodestroy', :collection => #album.photos, :as => :photo %>
<% end %>
</div>
I don't think you want to render the full url of an image as a class of a div. Also, your Jquery is wrong. Try something like this:
destroy.js.erb
$('.destroypicswrapper > #photo_<%= #photo.id %>').remove();
Adjust your _photodestroy.html.erb to:
<div class="picthumb" id="photo_<%= photo.id %>">

Rails 3 form_for ajax call

I have method in my controller:
def work_here
#company = Company.find(params[:id])
current_user.work_apply(current_user, #company)
redirect_to company_path
end
On my view:
<%= render 'companies/work_form' if signed_in? %>
_work_form.html.erb:
<%= form_for company, :remote => true, :url => { :controller => "companies", :action => "work_here" } do |f| %>
<%= f.hidden_field :id, :value => company.id %>
<%= f.submit "I working here" %>
<% end %>
And I have a work_here.js.erb file:
$("#work_at_form").html("<%= escape_javascript("render('companies/works')") %>")
But my form works without ajax request(in other pages ajax forks fine), my js.erb file never use.
Can anyone give me advise?
Thanks.
The work_here.js.erb can't be read because you never call it. A redirect is allways do. render it when the request is js format.
def work_here
#company = Company.find(params[:id])
current_user.work_apply(current_user, #company)
respond_to do |format|
format.html { redirect_to company_path }
format.js { render }
end
end

Resources