Solution to AbstractController::DoubleRenderError - ruby-on-rails

I've built a volunteer tracking application with a phone-text user interface using the Twilio API. I don't need a view so my controller contains this code:
class TwilioController < ApplicationController
include TwilioHelper
def sms_receive
user = User.find_or_create_by(phone_number: params[:From])
text = Text.create(user_id: user.id, body: params[:Body].capitalize, date: DateTime.now)
activity_log = ActivityLog.new(user_id: user.id, phone_number: "xxx-xxx-#{user.last_four_digits}", text_id: text.id)
args = {user: user, text: text, activity_log: activity_log, options: params}
volunteer_manager = VolunteerHandler.new(args)
replies = volunteer_manager.process
replies.each {|reply| text_response(reply, args[:options])}
end
def text_response(reply, args)
account_sid = ENV['ACCOUNT_SID']
auth_token = ENV['AUTH_TOKEN']
client = Twilio::REST::Client.new account_sid, auth_token
client.account.messages.create(:body => reply, :to => args[:From], :from => args[:To])
render nothing: true and return
end
end
A user will send a multi command string (i.e. 'In with American Red Cross'). In this case two commands will execute 'In' and 'with American Red Cross'. These commands return an array of strings such as ['Thank you for volunteering', 'Would you like the American Red Cross to keep in contact with you for future volunteering opportunities?']. This array is what local variable replies points to.
If I take off the render nothing:true and return code then I get the error: ActionView::MissingTemplate Missing template twilio/sms_receive
I can create the unnecessary view and solve my problem, but this doesn't seem like the best solution.
Any and all help is greatly appreciated. Thank you.

As replies is an array which is iterating over text_response its executing render nothing: true and return multiple times, which is cause of error you are facing.
Try getting render statement out of the loop.
class TwilioController < ApplicationController
include TwilioHelper
def sms_receive
user = User.find_or_create_by(phone_number: params[:From])
text = Text.create(user_id: user.id, body: params[:Body].capitalize, date: DateTime.now)
activity_log = ActivityLog.new(user_id: user.id, phone_number: "xxx-xxx-#{user.last_four_digits}", text_id: text.id)
args = {user: user, text: text, activity_log: activity_log, options: params}
volunteer_manager = VolunteerHandler.new(args)
replies = volunteer_manager.process
replies.each {|reply| text_response(reply, args[:options])}
render nothing: true and return
end
def text_response(reply, args)
account_sid = ENV['ACCOUNT_SID']
auth_token = ENV['AUTH_TOKEN']
client = Twilio::REST::Client.new account_sid, auth_token
client.account.messages.create(:body => reply, :to => args[:From], :from => args[:To])
end
end

Related

I'm able to hit my API for every request, but my POST request to create a new user

I'm working on authentication (using Knock) for my rails API and I can't hit my POST route to create a new user using a react client. I am able to hit this route in Postman and it successfully creates a new user. I can see I hit the route in postman from the terminal, but when I try it on my client I don't get any response in the terminal.
Things I've tried
Ensured client and server are running on different ports
I have my client on localhost:3000 and my server on localhost:3001
Made sure I have CORS set up
I am using the gem 'rack-cors' and have a cors initializer setup according to the docs for Rails 5. (I can include this file if you believe its needed)
I also have enabled CORS on my chrome web browser
Try another POST route
I am able to successfully log in on the client side (using a user's email/password I created in postman) and generate a JWT token using a POST route to my api
Made sure I am getting to the action creator from my container
I put a debugger in the action creator to make sure I am hitting it when I submit the form. I hit it and have all the relevant info I need (first name, last name, email, and password) to complete the request.
I think the problem lies somewhere in my userSignUpFetch action creator or in my user Controller.
Action creator that handle process of sending new user object to rails
export const userSignUpFetch = (user) => {
const newUser = user
return dispatch => {
return fetch(`http://localhost:3001/api/users`, {
method: "POST",
headers: {
Accept:"application/json",
"Content-Type":"application/json"
},
body: JSON.stringify({user: user})
})
.then(response => response.json())
.then(jresp => {
dispatch(loginUserFetch({
first_name: newUser.first_name,
last_name: newUser.last_name,
email: newUser.email,
password: newUser.password})
);
})
.catch((errors) => {
dispatch(authFailure(errors))
})
};
}
My current Routes for auth in Rails
api_users GET /api/users(.:format) api/users#index
POST /api/users(.:format) api/users#create
api_user GET /api/users/:id(.:format) api/users#show
PATCH /api/users/:id(.:format) api/users#update
PUT /api/users/:id(.:format) api/users#update
DELETE /api/users/:id(.:format) api/users#destroy
api_user_token POST /api/user_token(.:format) api/user_token#create
api_find_user POST /api/find_user(.:format) api/users#find
My Rails User Controller
class Api::UsersController < ApplicationController
before_action :set_user, only: [:show, :update]
def index
#users = User.all
render json: #users
end
def create
#user = User.create(user_params)
if #user.valid? && #user.save
render json: #user
else
render json: #user.errors, status: 400
end
end
def show
render json: #user
end
def update
if #user.update(user_params)
render json: #user
else
render json: #user.errors, status: 400
end
end
def destroy
#user.destroy
end
def find
#user = User.find_by(email: params[:user][:email])
if #user
render json: #user
else
#errors = #user.errors.full_messages
render json: #errors
end
end
private
def set_user
#user = User.find_by(id: params[:id])
end
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password)
end
end
I'm expecting to see something like
Started POST "/api/users" for ::1 at 2019-05-28 17:56:41 -0500
in my terminal, but when I hit that action creator I don't get any response from my terminal running the server. I'm wondering if anybody has any suggestions on what to look for. Thanks.
Update
I believe it is something with my dispatch.
This below works, up until jresp.loginUserFetch
export const userSignUpFetch = user => {
//Fetch request info
const newUser = JSON.stringify({user: user})
const userAuth = JSON.stringify({user})
const options = {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: newUser
}
const fetchURL = `${API_URL}/users`
return fetch(fetchURL, options)
.then(resp => resp.json())
.then(jresp => jresp.loginUserFetch({
first_name: userAuth.first_name,
last_name: userAuth.last_name,
email: userAuth.email,
password: userAuth.password}))
.catch( err => {
console.log('Request Failed:', err)
})
}
Update 2- Got it To work
I imported fetch from cross fetch as well as cleaned it up a little, but it works as attended now which makes me believe I needed cross-fetch in the file.
export const userSignUpFetch = user => {
//Fetch request info
const newUser = JSON.stringify({user: user})
const options = {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: newUser
}
const fetchURL = `${API_URL}/users`
return dispatch => {
return fetch(fetchURL, options)
.then(resp => resp.json())
.then(jresp => { dispatch(loginUserFetch({
first_name: user.first_name,
last_name: user.last_name,
email: user.email,
password: user.password})
)
})
.catch( err => {
console.log('Request Failed:', err)
})
}
}

How to send a get to a server and wait a get in my app in Ruby on Rails

I am using Zapier to search some information in google sheets. I used Webhocks to send a GET to his server with a JSON information. The response of GET is an "OK" and I can't custom this.
So, they will execute a task, find what a I want and return a value, but the response must be a GET in my server, and I don't know how to intercept this response in my route.
I'm trying to study Rails Rack to intercept de request in my app, but I don't know how to send the response to the event that sent the first GET.
How is my middleware:
class DeltaLogger
def initialize app
#app = app
end
def call env
Rails.logger.debug "#{env['QUERY_STRING']}"
#status, #headers, #response = #app.call(env)
[#status, #headers, #response]
end
end
Thanks!
Example
So, to get the value returned from Zapier, I created two routes and a global class cache.
class Zapier
require 'httparty'
def initialize
#answer = ""
#id = 0
end
def request(uri, task)
last_event = Event.last
puts last_event.inspect
if last_event.nil?
last_id = 0
else
last_id = last_event.event_id
end
event_id = last_id + 1
Event.find_or_create_by(event_id: event_id)
result = HTTParty.post(uri.to_str,
:body => {id: event_id, task: task}.to_json,
:headers => {'content-Type' => 'application/json'})
#answer = ""
#id = event_id
end
def response(event_id, value)
if event_id != #id
#answer = ""
else
#answer = value
end
end
def get_answer
#answer
end
end
And my controller:
class ZapierEventsController < ApplicationController
require 'zapier_class'
before_action :get_task, only: [:get]
before_action :get_response, only: [:set]
##zapier ||= Zapier.new
def get
##zapier.request('https://hooks.zapier.com',#task)
sleep 10 #Wait for response
#value = ##zapier.get_answer
render json: { 'value': #value }, status:
end
def set
##zapier.response(#id, #value)
render json: { 'status': 'ok' }, status: 200
end
def get_task
#task = params["task"]
end
def get_response
#id = Integer(params["id"])
#value = params["value"]
end
end
Now i have to make a Task Mananger

Define a params in a variable

I would know how define a params in a variable to use it in another method
In my controller i have result page and contact page, i want store the search params from result page in variables and get them in my contact page method to not duplicate form fields
My result page
def result
if params[:room_type].present? && params[:location].present? && params[:nb_piece].present?
#biens = Bien.near(params[:location], 1, units: :km).where(room_type: params[:room_type], nb_piece: params[:nb_piece])
end
#users = User.where(id: #biens.reorder(:user_id).pluck(:user_id), payer: true) || User.where(id: #biens.reorder(:user_id).pluck(:user_id), subscribed: true)
end
I want store this params in my other method,like that i will need to ask only email and phone in my form
def contact
wufoo(params[:location], params[:room_type], params[:nb_piece], params[:email], params[:phone])
end
My wufoo
def wufoo(adresse, type, pieces, email, phone)
require "net/http"
require "uri"
require "json"
base_url = 'https://wako94.wufoo.com/api/v3/'
username = 'N5WI-FJ6V-WWCG-STQJ'
password = 'footastic'
uri = URI.parse(base_url+"forms/m1gs60wo1q24qsh/entries.json")
request = Net::HTTP::Post.new(uri.request_uri)
request.basic_auth(username, password)
request.set_form_data(
'Field7' => adresse,
'Field9' => type,
'Field12' => email,
'Field11' => phone,
'Field8' => pieces
)
response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme =='https'){
|http|http.request(request)
}
puts JSON.pretty_generate(JSON[response.body])
end
It depends on how a user goes from search to contact. I assume that the contact form is linked off the search, and that they want to contact you regarding the information in the last search.
A simple method here would be to store the last search within the session, and just reference that.
def search
store_params_in_session
# .. your search logic here
end
def contact
last_search = session[:last_search]
if last_search.blank?
# .. some error handling if no search is available
return
end
wufoo(last_search[:location], #.. you get the idea
end
private
def store_params_in_session
session[:last_search] = {
location: params[:location],
# .. more params here
}

HGET in private method not returning hash

Some reason my hget is not finding or returning a hash I set in a public method. I can't figure out why.
This is all in one controller that inherits from ApplicationController, which is where I define my redis initializer:
def redis
Thread.current[:redis] ||= Redis.new
end
Then in my controller I do this to set the hash:
def return_customer
email = params["email"]
customer = Customer.find_by(email: email)
credit_amount = customer.credit_amount.to_f
customer_data = {email: email, customer_id: customer.id, credit_amount: credit_amount}
redis.hset("shop:#{customer.shop.id}:customer", customer_data, customer_data.inspect)
render json: customer
end
Then finally I have this private method I use in other methods in the same controller, this is the part that's not working:
private
def get_customer_from_redis
shop = Shop.find_by(shopify_domain: params["shop"])
customer_info = redis.hget("shop:#{shop.id}:customer", customer_data)
eval(customer_info)
end
This is the error that's returned
TypeError (no implicit conversion of nil into String):
I'd recommend you rather than using .inspect use .to_json like this:
def return_customer
email = params["email"]
customer = Customer.find_by(email: email)
credit_amount = customer.credit_amount.to_f
customer_data = {email: email, customer_id: customer.id, credit_amount: credit_amount}
redis.set("shop:#{customer.shop.id}:customer", customer_data.to_json)
render json: customer
end
And then in your private method
def get_customer_from_redis
shop = Shop.find_by(shopify_domain: params["shop"])
customer_info = redis.get("shop:#{shop.id}:customer", customer_data)
JSON.parse(customer_info) if customer_info
end

Cannot pass params to sidekiq

I am building a worker for a controller action, but sidekiq will not boot due to me calling params in the perform method. Any ideas on how to get this to work?
controller
def call_warrants_with_date_range
CallLogWorker.perform_async(params[:call_log])
redirect_to call_logs_path, notice: 'Calls were successfully made.'
end
worker
class CallLogWorker
include Sidekiq::Worker
def perform(params[:call_log])
client = Twilio::REST::Client.new TWILIO_ACCOUNT_SID, TWILIO_ACCOUNT_AUTH_TOKEN
start_date = params[:call_log][:warrant_start_date]
end_date = params[:call_log][:warrant_end_date]
query = "SELECT people.id, warrants.warn_type, warrants.warn_date_issued, phone_numbers.phone_number
FROM people
LEFT OUTER JOIN warrants ON people.id = warrants.person_id
LEFT OUTER JOIN phone_numbers ON people.id = phone_numbers.person_id
WHERE warrants.warn_date_issued BETWEEN ? AND ? AND warrants.warn_type = 'AW'"
#numbers = CallLog.find_by_sql(["#{query}", start_date, end_date])
#numbers.each do |dial|
begin
call = client.account.calls.create(
:from => TWILIO_PHONE_NUMBER,
:to => dial.phone_number,
:url => 'http://twimlets.com/echo?Twiml=hello%20this%20is%20a%20test%20call%20please%20hang%20up&'
)
CallLog.create!({ phone: dial.phone_number, status: call.status,
warrant_start_date: start_date, warrant_end_date: end_date, person_id: dial.id})
Note.create!({ body: call.status, person_id: dial.id })
rescue Exception => e
CallLog.create!({ phone: dial.phone_number, status: call.status, exception: e.to_s,
warrant_start_date: start_date, warrantend_date: end_date, person_id: dial.id})
Note.create!({ body: e.to_s, person_id: dial.id })
end
end
end
end
In your worker:
def perform(params)
start_date = params[:call_log][:warrant_start_date]
end_date = params[:call_log][:warrant_end_date]
...etc
end
And then in your controller:
CallLogWorker.perform_async(params)
So you're parsing the hash params into the worker from the controller and then referring to it in your worker.
It's generally considered good practice to keep the data you pass into Sidekiq jobs as small as possible - see here for best practices. So you could go further and have:
In your worker:
def perform(start_date, end_date)
...job content
end
And in your controller:
CallLogWorker.perform_async(
params[:call_log][:warrant_start_date],
params[:call_log][:warrant_end_date]
)

Resources