Javascript action not triggering in Capybara test - ruby-on-rails

I have an instant search-as-you-type implemented with Turbo and a Stimulus controller, as outlined in this post. Relevant JavaScript, in app/javascript/controllers/form_submission_controller.js:
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
search() {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.element.requestSubmit()
}, 200)
}
}
and in the view, something like:
<%= form_with(url: users_path, method: :get, data: { controller: "form-submission", turbo_frame: "users", turbo_action: "advance" }) do |form| %>
<%= form.label :query, "Search by name:" %>
<%= form.text_field :query, data: { action: "input->form-submission#search" } %>
<% end %>
It works in my browser, but not in my test suite. I'm using RSpec + Capybara with Poltergeist driver, and it seems that when using fill_in to enter the query, this doesn't trigger the Stimulus controller to update the page; the entire list is still there, and there is no HTTP request for the search in the logs. How can I trigger this controller function from the test suite?

The poltergeist driver has been discontinued for 4 years and is basically equivalent to a 6 year old version of Safari, at best. Because of that the most likely issue here is that your page is using JS poltergeist doesn’t support. Stop using poltergeist and move to an updated/supported driver (selenium, etc)

Related

How to add a user input to re-calculate code in controller and then show in the same view?

Looking to be pointed in the right direction. I have a simple form in my view that accepts a number. I am wanting to send the input of this to be used in a calculation in my controller and then update for the user to see in real time.
index.html.erb
Simple input form, followed by calculation output:
<%= form_with do |form| %>
<%= form.text_field :number %>
<%= form.submit "Submit" %>
<% end %>
<%= #output %>
Calculation in controller:
#output = params[:number].to_i * 2
The code is just a stupid example, but the idea is: User input in the view, which I could then place in a calculation in my controller and then send back to view. I don't need a database as I don't need to store any information.
After researching, a lot of people are suggesting Ajax for this in rails? Is this the way to go or is there something in Ruby that I can use for the same effect. I've also seen Tableless Models being used. What would the best solution be here for me? I'm guessing recalculating the code in controller is the key bit.
Yes, you can do this with AJAX. In Rails, you can use a Stimulus controller to do that.
Below is a simple demonstration:
I have a controller called pages and it looks like this.
class PagesController < ApplicationController
def index
end
def receive
render json: { body: params }
end
end
Then pages#index view should be like this.
<div class="input-form" data-controller="ajax">
<%= form_with url: receive_path, data: { ajax_target: 'form', action: 'submit->ajax#send' } do |form| %>
<%= form.text_field :number %>
<%= form.submit "Submit" %>
<% end %>
<p data-ajax-target="output"></p>
</div>
It has your form and p tag with data-ajax-target="output" is the element where you display form input data.
Then you generate a new stimulus controller caller ajax and put something like this.
export default class extends Controller {
static targets = ['form', 'output']
connect() {
console.log(this.formTarget);
console.log(this.outputTarget);
}
send(event) {
event.preventDefault();
fetch(this.formTarget.action, {
method: 'POST',
headers: { "Accept": "application/json" },
body: new FormData(this.formTarget)
})
.then(response => response.json())
.then(data => {
this.outputTarget.innerHTML = data.body.number * 2;
});
}
}
And you need to make sure that you define a post route.
Rails.application.routes.draw do
root 'pages#index'
post 'receive', to: 'pages#receive'
end
Hope that make sense.
Parameters send from the browser to a Ruby on Rails app are available to the controller via the params method.
Your controller method will likely look like this:
#output = params[:number].to_i * 2
Note: I also added a to_i to ensure that the user input is not used in its string representation, but actually translated into a number.

Update View After Destroying Item from Database

Right now I am displaying notifications for a user on a view. I have the ajax working where it removes the item from the database by clicking the close link. However, what I can't figure out is how to have the view update without actually refreshing the page.
Am I going to have to use a partial of some sort? I haven't really done anything like this and googling is really not helping me since I seem to be going down the rabbit hole.
Right now for my view I have:
<% current_user.notifications.each do |n| %>
<div class="alert alert-info">
<%= link_to 'x', n, :method => :delete, :remote => true, :class => 'delete_notification close' %>
<%= n.title %>
<%= n.description %>
</div>
<% end %>
and then I have my js:
$('.delete_notification').bind('ajax:success', function() {
$(this).closest('tr').fadeOut();
});
I'm not really sure how to ask the question since I am lost on what the 'next step' is. I will provide more information if/when I know what else I need to provide.
Thanks in advance!
UPDATE:
I accepted an answer seeing as it was extremely helpful and exactly what I was looking for!
What I ended up comming up with is:
$(function(){
$(".delete_notification").click(function(){
var $this = $(this);
var url = $this.data('url');
$.ajax(url, {
method: 'DELETE',
type: 'POST',
dataType: 'json',
success: function() {
var parent = $this.parent();
parent.fadeOut("slow");
}
});
});
});
The link_to's callback option (:update) is removed since Rails 3, so I'd rather implement the AJAX link myself.
<% current_user.notifications.each do |n| %>
<div class="alert alert-info">
<%= link_to 'x', n, :class => 'delete_notification close' %>
<%= n.title %>
<%= n.description %>
</div>
<% end %>
Setup AJAX so that it passes Rails' CSRF protection:
$.ajaxSetup({
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))}
});
And finally the effect you want:
$('.delete_notification').click(function(ev) {
ev.preventDefault(); // prevent refreshing page
var $this = $(this);
var url = $this.attr('href');
$.ajax(url, {method: 'DELETE'}).done(function() {
$this.closest('tr').fadeOut();
});
});
Maybe you should also modify your controller (I haven't tested this):
class NotificationsController < ApplicationController
# Other actions ...
def destroy
# Your logic (remove rendering or redirection)
head :no_content
end
end
Create a js template in your view called destroy.js.erb, and in that file include the javascript you want to run - something like this:
$('#delete_notification_<%= params[:id] %>').closest('tr').fadeOut();
In your view make sure you specify the ID of the link:
<%= link_to 'x', n, method: :delete, remote: true, class: 'delete_notification close' id: "delete_notification_#{n.object.id}" %>

I am trying to rewrite a button_to

I'm trying to rewrite the following button code so that instead of redirecting me to the show page, it just creates the object and the current page stays displayed.
<div class="colors">
<li class="colors" id="greys">
<%= button_to "some text", votes_path(color: 'grey', kid_id: current_kid, scoop_id: scoop.id, :method => :create), class: 'grey color-button' %>
</li>
</div>
You should use a remote flag to send the request via javascript. And possibly give feedback to the user.
To send a request via js in rails you have to add remote: true to the options hash:
<div class="colors">
<li class="colors" id="greys">
<%= button_to "some text", votes_path(color: 'grey', kid_id: current_kid, scoop_id: scoop.id, :method => :create), class: 'grey color-button', remote: true %>
</li>
</div>
In your controller you can do special responses by respondig to js
#votes controller
def create
# ...
respond_to do |format|
format.js render
end
end
In your create.js.erb you can write javascript code with embeded ruby to give responses. You can even render partials here.
alert('create.js.erb')
First, have some id for your button:
<%= button_to "some text", votes_path(color: 'grey', kid_id: current_kid, scoop_id: scoop.id, :method => :create), class: 'grey color-button' id: 'buttonID' %>
And then, in your Javascript code, whenever the button is clicked, send a request to server, create the new record, and then update the current page, so that it shows the record has been created.
I would show you the sculpture of how to do it:
$("buttonID").click(function() {
$.ajax({
url: "some_url" // Your URL, for example, /votes
type: "POST", // The type of request
data: { color: "grey" } // Send data to server in this format
success: function() {
// Here you can update the page so that the user could
// know the data has been posted, and no need to press
// the button again
}
});
});

Dynamically determine get or post call in form_tag

I am new to ruby on rails and I have a fundamental question. I have a form which has been defined via ruby inject which looks like below,
<%= form_tag("/#{params['controller']}/index", :method => "get", :class => "search", :id => "fSearch" ) do %>
<%= render 'fsearch_filter' %>
<%end %>
In fsearch_filter.html.erb, I have a text area which looks like this:
<%= text_area_tag 'filter[fno]', #default[:fno], rows: 10, cols: 20, class: 'formlabel' %>
So basically I want the form "fSearch" to use a "get" call to talk to the server, if the text area "filter[fno]" is empty, else it should use a "post" call. Is there any way I can do this with ruby on rails ?
The below solution may help you....
function changeMethod() {
var text_info = $("#filter_fno").val();
if (text_info != "")
{
$("#fSearch").attr("method", "post");
}
}
Call the above JavaScript method on form submission.

Make sure crawlers don't 'click' on button

I have the following code in my view:
= form_tag(new_demo_path, :method => "put", id: "demo-form") do
= submit_tag "Try out demo", :name => nil
This will setup a demo environment which will be accessible for 7 days. Of course I don't want search engines to click this. Since it's a put request, will this be a problem? Do I need to include something so they don't click this link?
Some time ago I found myself in a similar situation and I wrote a negative captcha plugin for Rails: https://github.com/markets/invisible_captcha. It's based on the honeypot strategy, the idea was to provide a better user experience.
More or less, you should add into your form:
<%= form_tag(new_demo_path, :method => "put", id: "demo-form") do %>
<%= invisible_captcha %>
<% end %>
In your controller:
invisible_captcha only: [:new_demo] # assuming new_demo is the action that handles the form

Resources