ActionCable not getting data on Rails - javascript

I am trying to build a chat feature in my app and initially, the action cable had worked for it seems like it is not getting any data to broadcast. I have tried to console log in the chatroom_channel.js but the "data" is not being populated on the console.
initChatroomChannel is also exported from application.js
I have also tried to use the solution from the ticket below but that also did not help out.
ActionCable Not Receiving Data
There's are the related files.
Thank you
Onur
messages_controller
class MessagesController < ApplicationController
def create
#chatroom = Chatroom.find(params[:chatroom_id])
#message = Message.new(message_params)
#message.chatroom = #chatroom
#message.user = current_user
if #message.save
ChatroomChannel.broadcast_to(
#chatroom,
render_to_string(partial: "message", locals: { message: #message })
)
redirect_to chatroom_path(#chatroom, anchor: "message-#{#message.id}")
else
render "chatrooms/show"
end
end
def message_params
params.require(:message).permit(:content)
end
chatrooms_controller
class ChatroomsController < ApplicationController
def index
#chatrooms = []
all_chatrooms = Chatroom.all
all_chatrooms.each do |chatroom|
#chatrooms << chatroom if (current_user.id == chatroom.engager_id || current_user.id == chatroom.receiver_id)
end
end
def show
chat = Chatroom.find(params[:id])
if (current_user.id == chat.engager_id || current_user.id == chat.receiver_id)
#message = Message.new
#chatroom = chat
else
redirect_to chatrooms_path
end
end
def destroy
#chatroom = Chatroom.find(params[:id])
#chatroom.destroy
redirect_to chatrooms_path
end
end
chatroom_channel.rb
class ChatroomChannel < ApplicationCable::Channel
def subscribed
# stream_from "some_channel"
chatroom = Chatroom.find(params[:id])
stream_for chatroom
end
# def unsubscribed
# # Any cleanup needed when channel is unsubscribed
# end
end
chatroom_channel.js
import consumer from "./consumer";
const initChatroomCable = () => {
const messagesContainer = document.getElementById('messages');
if (messagesContainer) {
const id = messagesContainer.dataset.chatroomId;
consumer.subscriptions.create({ channel: "ChatroomChannel", id: id }, {
received(data) {
console.log(data)
messagesContainer.insertAdjacentHTML("beforeend", data)
},
});
}
}
export { initChatroomCable };
application.js
import Rails from "#rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "#rails/activestorage"
import "channels"
Rails.start()
Turbolinks.start()
ActiveStorage.start()
// External imports
import "bootstrap";
import { loadDynamicBannerText } from '../componets/banner';
// Internal imports, e.g:
import { initChatroomCable } from '../channels/chatroom_channel';
document.addEventListener('turbolinks:load', () => {
initChatroomCable();
jQuery.fn.carousel.Constructor.TRANSITION_DURATION = 2000
var $item = $('.carousel-item');
var $wHeight = $(window).height();
$item.eq(0).addClass('active');
$item.height($wHeight * 0.75);
$item.addClass('full-screen');
$('.carousel img').each(function () {
var $src = $(this).attr('src');
var $color = $(this).attr('data-color');
$(this).parent().css({
'background-image': 'url(' + $src + ')',
'background-color': $color
});
$(this).remove();
});
$(window).on('resize', function () {
$wHeight = $(window).height();
$item.height($wHeight * 0.75);
});
$('.carousel').carousel({
interval: 6000,
pause: "false"
});
const banner = document.getElementById("banner-typed-text")
if (banner) {
loadDynamicBannerText();
}
});
import "controllers"
_message.html.erb
<div class="message-container" id="message-<%= message.id %>">
<i class="author">
<% User.find(message.user_id).name ? name = User.find(message.user_id).name : name = "Unknown Cat" %>
<span><%= User.find(message.user_id) == current_user ? "me" : name %></span>
<small><%= message.created_at.strftime("%a %b %e at %l:%M%p") %></small>
</i>
<p><%= message.content %></p>
</div>
cable.yml
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: Cat_Tinder_production

Related

Setting state when page loads, Depending on (database?) contents

So in my code, I want it where a single User can only have one booking/reservation per Hotel, but every time after the page is refreshed, my state keeps reverting back to it's default value, even if the Hotel has already been reserved...
BTW, all of the code is functioning, it is just the state that is giving me a problem...
I've been troubleshooting and debugging the code for a couple of hours but nothing concrete so far.
I've included code from most of the files, but I can't bring in the code from all of the ones needed, or it wouldn't quite be an "minimal" reproducible example, so I'll try to summarize the structure here:
There is a many-to-many relationship between Users and Hotels, which has Bookings belonging to both. This enables me to use hotel.users, etc.
I've just tried adding in a new state (const [bookingObject, setBookingObject] = useState();), but I haven't been able to get it to give me a truthy value 100% of the time... Can anyone see what it is that I'm doing wrong? Anyone have any suggestions or ideas?
bookings_controller.rb:
class BookingsController < ApplicationController
skip_before_action :authorize, only: [:create, :destroy]
def show
render json: #booking
end
def create
booking = Booking.new(booking_params)
if booking.save
render json: booking, status: :created
else
render json: {errors: "Something went wrong!"}
end
end
def destroy
#booking = Booking.find(params[:id])
#booking.destroy
end
private
def set_booking
#booking = Booking.find(params[:id])
end
def booking_params
params.require(:booking).permit(:id, :user_id, :hotel_id)
end
end
hotels_controller.rb:
class HotelsController < ApplicationController
skip_before_action :authorize, only: [:create, :destroy]
def index
render json: Hotel.all
end
def show
render json: #hotel
end
def hotel_params
params.require(:hotel).permit(:name, :city, :country, :company)
end
end
Hotel.jsx:
import React, {useContext, useState} from 'react';
const {bookings, setBookings} = useContext(BookingsContext);
const [booked, setBooked] = useState(false);
const [bookingObject, setBookingObject] = useState();
function toggleBooking(e){
if(booked){
const booking = bookings.find(booking => {return booking.hotel.id == e.target.id});
deleteBookings(booking.id);
setBooked(!booked);
} else if(booked === false){
postBookings();
setBooked(!booked);
}
}
function postBookings(){
const newBooking={
user_id: (currentUser.id),
hotel_id: (hotel.id)
}
fetch(`http://localhost:3001/users/${currentUser.id}/bookings`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
booking: newBooking
}),
}).then((r) => r.json())
.then((data) => {
setBookingObject(data);
setBookings([...bookings, data]);
})
}
function deleteBookings(bookingId){ fetch(`http://localhost:3001/users/${currentUser.id}/bookings/${bookingId}`, {
method: "DELETE"
})
.then((r) => {
if(r.ok)onDeleteBookings(bookingId);
})
}
function onDeleteBookings(deletedBooking){
const updatedBookings = bookings.filter((booking) => booking.id !== deletedBooking.id);
setBookings(updatedBookings);
setBookingObject();
}
return(
<div id='hotels'>
<p>{hotel.name}</p><p>{hotel.city}, {hotel.country}</p>
<button id={hotel.id} onClick={toggleBooking} >{booked ? ("Booked") : ("Book Now")}</button>
</div>
);
}
export default Hotel;

RoR, Stimulus.js: A simple input calculation?

I am new to stimulus. I am trying to add up number/currency inputs and display in another field (the fields are decimal attributes) as the user types in their values but I can't get it to work.
Here I want to add 'land cost' + 'prof services' and display it in 'total cost' field.
My controller:
cost_calculate.controller.js
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = [ 'landCost', 'profServices', 'totalCost' ]
updateTotalCost () {
const totalCost = this.landCost() + this.profServices()
this.totalCostTarget.value = Number(totalCost).toFixed(2)
}
landCost () {
return parseInt(this.landCostTarget)
}
profServices () {
return parseInt(this.profServicesTarget)
}
totalCost () {
return parseInt(this.totalCostTarget)
}
}
My form:
<%= simple_form_for #project, html: { "data-controller" => "cost-calculate" }, url: wizard_path(step, project_id: #project.id) do |f| %>
<%= f.text_field :land_cost, data: { target: 'cost-calculate.landCost', action: "change->cost-calculate#updateTotalCost" }, class: "project-dropdown-width" %>
<%= f.text_field :prof_services, data: { target: 'cost-calculate.profServices', action: "change->cost-calculate#updateTotalCost" }, class: "project-dropdown-width" %>
<%= f.text_field :total_cost, data: { target: 'cost-calculate.totalCost' }, label: false, class: "project-dropdown-width" %>
<% end %>
It keeps printing NaN in the 'totalCost' field. I'm not entirely confident my code is right in controller or view either for what I want to do.
For example, I want to achieve this but just adding two fields together
https://ngaunhien.net/blog/simple-input-calculation-with-stimulusjs
Would appreciate any guidance. ty.
You should use the value and parse and set the default value of 0 if no value exists.
You should modify the controller as
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = [ 'landCost', 'profServices', 'totalCost' ]
updateTotalCost () {
const totalCost = this.landCost() + this.profServices()
this.totalCostTarget.value = Number(totalCost).toFixed(2)
}
landCost () {
return parseInt(this.landCostTarget.value || 0)
}
profServices () {
return parseInt(this.profServicesTarget.value || 0)
}
totalCost () {
return parseInt(this.totalCostTarget.value)
}
}

rails vue.js deep nested (attributes of attribute)

I created one nested form by watching gorails tutorial It is fine and i done it. Issue started when i want to creat nested model under on other nested model. I have Survey model and it is main model. Then i added Question model and made form with vue.js. So I added Choice model under question ( you can notice in survey controller params) First problem is; i don't know how i can define/implemen in vue.js control.(hello_vue.js) And second importan point is: how i can create form elements in new.html
This is my survey.rb model:
class Survey < ApplicationRecord
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions, allow_destroy: true
belongs_to :user
end
and surveys_controller.rb
class SurveysController < ApplicationController
before_action :set_survey, only: [:show, :edit, :update, :destroy]
def survey_params
params.require(:survey).permit(:user_id, :name, questions_attributes:[:id,:survey_id, :title, :qtype, :_destroy, choices_attributes:[:id,:question, :ctext]])
end
end
This is nested model of Survey : question.rb:
class Question < ApplicationRecord
enum qtype: [:multiple_choice, :check_boxes, :short_answer]
belongs_to :survey
has_many :choices
accepts_nested_attributes_for :choices, allow_destroy: true
end
So finaly vue.js file:
import TurbolinksAdapter from 'vue-turbolinks'
import Vue from 'vue/dist/vue.esm'
import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.use(TurbolinksAdapter)
Vue.component('app', App)
document.addEventListener('turbolinks:load', () => {
Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
var element = document.getElementById("survey-form")
if (element != null){
var survey = JSON.parse(element.dataset.survey)
var questions_attributes = JSON.parse(element.dataset.questionsAttributes)
var choices_attributes = JSON.parse(element.dataset.choicesAttributes)
questions_attributes.forEach(function(question) { question._destroy = null })
survey.questions_attributes = questions_attributes
var app = new Vue({
el: element,
//mixins: [TurbolinksAdapter],
data: function(){
return { survey: survey }
},
methods:{
addQuestion: function(){
this.survey.questions_attributes.push({
id: null,
title:"",
qtype:"",
_destroy: null
})
},
removeQuestion: function(index) {
var question = this.survey.questions_attributes[index]
if (question.id == null) {
this.survey.questions_attributes.splice(index, 1)
} else {
this.survey.questions_attributes[index]._destroy = "1"
}
},
undoRemove: function(index) {
this.survey.questions_attributes[index]._destroy = null
},
saveSurvey: function() {
// Create a new survey
if (this.survey.id == null) {
this.$http.post('/surveys', { survey: this.survey }).then(response => {
Turbolinks.visit(`/surveys/${response.body.id}`)
}, response => {
console.log(response)
})
// Edit an existing survey
} else {
this.$http.put(`/surveys/${this.survey.id}`, { survey: this.survey }).then(response => {
Turbolinks.visit(`/surveys/${response.body.id}`)
}, response => {
console.log(response)
})
}
},
existingSurvey: function() {
return this.survey.id != null
}
}
})
}
})
_form.html.erb
<%= content_tag :div,
id: "survey-form",
data: {
survey: survey.to_json(except: [:created_at, :updated_at]),
questions_attributes: survey.questions.to_json,
} do %>
<label>Survey Name</label>
<input qtype="text" v-model="survey.name">
<h4>Questions</h4>
<div v-for="(question, index) in survey.questions_attributes">
<div v-if="question._destroy == '1'">
{{ question.title }} will be removed. <button v-on:click="undoRemove(index)">Undo</button>
</div>
<div v-else>
<label>Question</label>
<input qtype="text" v-model="question.title" />
<label>Qestion qtype</label>
<select v-model="question.qtype">
<option v-for="qtype in <%= Question.qtypes.keys.to_json %>"
:value=qtype>
{{ qtype }}
</option>
</select>
<button v-on:click="removeQuestion(index)">Remove</button>
</div>
<hr />
</div>
<button v-on:click="addQuestion">Add Question</button>
<br>
<button v-on:click="saveSurvey" >Save Survey</button>
<% end %>
I followed this same tutorial and started running into issues using JSON.parse with more complex nested attributes. Try using Jbuilder to build your JSON objects and look into the gon gem to pass your Rails variables into Javascript. It'll be much easier to query your database and pass the results into your Javascript file using the nested naming that Rails needs. For example...
survey = #survey
json.id survey.id
json.survey do
json.(survey, :user_id, :name)
json.questions_attributes survey.questions do |question|
json.(question, :id, :title, :qtype, :_destroy)
json.choices_attributes question.choices do |choice|
json.(choice, :id, :ctext)
end
end
end
It allows you to do things like...
var survey = gon.survey
Instead of...
var survey = JSON.parse(element.dataset.survey)
And you can pass gon.jbuilder from your controller action and have your defined JSON object ready and available in Vue.

AJAX GET request in Rails without rendering views/json

Making a GET request to check whether or not to send a followup POST request and don't need to render anything following the first request. I'm using render nothing: true at the base of my controller action, so I'm not sure what's causing the ActionView::MissingTemplate at /able_to_claim error when I sent the request through the browser console:
def able_to_claim
role, user_id = params[:role], params[:user_id]
if role == "Senior Editor" or role == "Admin"
return true
else
active_essays = 0
Claim.where(:user_id => user_id).each {|claim| active_essays += 1 if Essay.find(claim.essay_id).status != "Complete"}
if role == "Editor" and active_essays < 5
return true
elsif role == "Managing Editor" and active_essays < 15
return true
else
return false
end
end
render nothing: true
end
Route: get '/able_to_claim' => 'users#able_to_claim'
js file:
response = $.ajax({
type: 'GET',
url : '/able_to_claim/?role=' + userRole + '&user_id=' + userId,
crossDomain: true,
contentType:'application/json; charset=utf-8',
dataType: 'json'
});
userRole and userId defined in application.html.erb:
<%= javascript_tag "var userId = #{current_user.id};" if current_user %>
<%= javascript_tag "var userRole = '#{current_user.role}';" if current_user %>
Wow, feel really silly. I had no idea that return breaks out of the rest of the function. With some refactoring:
def able_to_claim
role, user_id = params[:role], params[:user_id]
if role == "Senior Editor" or role == "Admin"
can_claim = true
else
active_essays = User.find(user_id).essays.where.not(status: 'Complete').count
if role == "Editor" and active_essays < 5
can_claim = true
elsif role == "Managing Editor" and active_essays < 15
can_claim = true
else
can_claim = false
end
end
render json: {can_claim: can_claim}
end

Uservoice Widget for Rails 3 App

I'm having trouble displaying the Uservoice widget on my page. I copied the javascript code from the Uservoice admin, converted it to HAML then pasted on to my app:
layouts/_uservoice.html.haml
:javascript
(function(){
var uv=document.createElement('script');
uv.type='text/javascript';uv.async=true;
uv.src='//widget.uservoice.com/Qho4ZF2W4O43bJ8Opc65g.js';
var s=document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(uv,s)
})()
:javascript
UserVoice = window.UserVoice || [];
UserVoice.push(['showTab', 'classic_widget', {
mode: 'full',
primary_color: '#cc6d00',
link_color: '#007dbf',
default_mode: 'support',
forum_id: 216289,
tab_label: 'Feedback & Support',
tab_color: '#cc6d00',
tab_position: 'middle-right',
tab_inverted: false
}]);
and my home view:
<doctype html>
%html
%head
= render 'shared/meta'
= favicon_link_tag 'tgnd_favicon.png'
%body
= render 'shared/fb'
#main{:role => "main"}
= render 'shared/header'
= render 'shared/sub_header'
.content_wrap.container
.content.box_normal
= render 'layouts/messages'
= yield
/ ! end of .container
.push
= render 'layouts/uservoice'
= render 'shared/footer'
= render 'shared/dialogs'

Categories

Resources