Cannot pass params to sidekiq - ruby-on-rails

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]
)

Related

How to best work with bulk data json render in rails?

I have a create method that redirects to host_calendar_path. But what could be the best way so that I could render json response with rv_date instead of single rv_date.
def create
start_date = Date.parse(rv_date_params[:start_date])
end_date = Date.parse(rv_date_params[:end_date])
(start_date..end_date).each do |day|
rv_date = RvDate.where(rv_id: rv_date_params[:rv_id], start_date: day)
if rv_date.present?
rv_date.update_all(price: rv_date_params[:price], status: rv_date_params[:status])
else
rv_date = RvDate.create(rv_id: rv_date_params[:rv_id], start_date: day, end_date: day, price: rv_date_params[:price], status: rv_date_params[:status])
end
end
redirect_to host_calendar_path(rv_id: rv_date_params[:rv_id])
end
Is storing those to array and sending rv_date as array a good solution?
Eg:
def create
start_date = Date.parse(rv_date_params[:start_date])
end_date = Date.parse(rv_date_params[:end_date])
rv_dates = []
(start_date..end_date).each do |day|
#rv_date = RvDate.where(rv_id: rv_date_params[:rv_id], start_date: day)
if #rv_date.present?
#rv_date.update_all(price: rv_date_params[:price], status: rv_date_params[:status])
else
#rv_date = RvDate.create(rv_id: rv_date_params[:rv_id], start_date: day, end_date: day, price: rv_date_params[:price], status: rv_date_params[:status])
end
rv_dates << #rv_date
end
# redirect_to host_calendar_path(rv_id: rv_date_params[:rv_id])
render json: rv_dates: rv_dates
end
This would work, yes. Ideally, you would create a new record, called EventSeries or something, and have all the logic to create the event instances inside EventSeries.
You would end up with something like
post "/event_series.json", params: {start_date: some_date, end_date: some_date}
Then, a event_series_controller where:
def create
#event_series = EventSeries.new(event_series_params)
if #event_series.save
render json: #event_series
end
end
And then, inside EventSeries class:
has_many :events
before_create :prepare_events
def prepare_events
(start_date..end_date).each do |day|
... code to manage events creation ...
end
end

Searchkick aggregations for has_and_belongs_to_many

I have a Rails 5 app and I'm trying to do an aggregations search for a has_and_belongs_to_many association.
Here is the code that I have so far:
event.rb:
class Event < ApplicationRecord
searchkick text_start: [:title]
has_and_belongs_to_many :services
has_and_belongs_to_many :sectors
def search_data
atributes.merge(
title: title,
description: description,
sector_name: sectors.map(&:name),
service_name: services.map(&:name)
)
end
end
events_controller.rb:
def index
query = params[:j].presence || "*"
conditions = {}
conditions[:sector] = params[:sector] if params[:sector].present?
conditions[:service] = params[:service] if params[:service].present?
conditions[:date] = params[:date] if params[:date].present?
#events = Event.search query, where: conditions, aggs: [:sector, :service, :date], order: {created_at: {order: "desc"}}, page: params[:page], per_page: 10
end
When I call Event.reindex in the console I was expecting to to show that the sectors and services had been indexed but it doesn't work.
To be honest I'm getting quite lost and going round in circles so any help would be much appreciated.
This is the code that ended up working for me:
event.rb:
def index
query = params[:j].presence || "*"
conditions = {start_date: {"gte": "now/d"}}
conditions[:sector_name] = params[:sector_name] if params[:sector_name].present?
conditions[:service_name] = params[:service_name] if params[:service_name].present?
conditions[:start_date] = params[:start_date] if params[:start_date].present?
#events = Event.search query, where: conditions, aggs: [:sector_name, :service_name], order: {start_date: {order: "asc", unmapped_type: "long"}}, page: params[:page], per_page: 10
end
events_controller.rb:
def search_data
{
title: title,
location: location,
description: description,
start_date: start_date,
sector_name: sectors.map(&:name),
service_name: services.map(&:name)
}
end

Update value with first_or_create in rails

I have a table 'Likes' with columns business_id, user_id and liked(0,1) and a function 'change_like_status'.
Now on every function call, If the value is 1 then set it to 0 (or vice versa) and if record doesn't exists then create one with value 1.
The first_or_create method is working just fine but how can i toggle value of column 'liked' while using this method?
Here is my function:
def change_like_status
if current_user.present?
status = Like.where("business_id = ? AND user_id = ?",params['id'],current_user.id).first_or_create(:business_id => params['id'],:user_id => current_user.id,:liked => '1')
abort status.inspect
else
return render :json => {:status => false,:msg=>"You need to sign in before performing this action."}
end
end
In you controller, make the changes
def change_like_status
if current_user
status = Like.create_or_change_status(params[:id], current_user.id)
else
return render json: { status: false, msg: "You need to sign in before performing this action." }
end
end
In your model like.rb file, add a method
def self.create_or_change_status(business_id, user_id)
status = where(business_id: business_id, user_id: user_id).first
if status.nil?
status = create({business_id: business_id, user_id: user_id, liked: 1})
else
status.update_attributes(liked: !status.liked)
end
status
end
def change_like_status
if current_user
current_user.likes.find_by(business_id: params[:id]).switch_status!
else
return render json: { status: false, msg: "You need to sign in before performing this action." }
end
end
class Like
def switch_status!
self.update_column :liked, !liked
end
end
other approach should be something like that
class Like
def switch_status!
self.update_column :liked, !liked
end
end
class User
def likes id
likes_for_business id
end
def likes_for_business(id)
likes.find_by(business_id: id) || likes.create(:business_id: id, liked: true)
end
end
# controller
current_user.likes(params[:id]).switch_status!

How to Add an Error Message when using a Virtual Attribute

I have a date field that is not a required field. I am using Chronic to format the user input string to a valid rails format for a date field. If Chronic is unable to parse the date, I would like to raise an error, rendering the edit view with the respective error message and the originally input value. Currently the update is successful if an invalid date is entered but nothing is updated for the service_date field.
new.html.erb
<%= f.text_field :service_date_text %>
bill.rb
require 'chronic'
class Bill < ActiveRecord::Base
def service_date_text
service_date.try(:strftime, "%m/%d/%Y")
end
def service_date_text=(date)
if date.present?
if Chronic.parse(date)
self.service_date = Chronic.parse(date)
else
self.errors.add(:service_date_text, "invalid date format hello.")
end
else
self.service_date = ''
end
end
end
bills_controller.rb
def update
#bill = current_account.bills.find(params[:id])
if #bill.update_attributes(bill_params)
redirect_to #bill, notice: 'Bill has been successfully updated.'
else
render :edit
end
end
private
def bill_params
params.require(:bill).permit(:description, :notes, :po_number, :service_date_text)
end
errors is cleared whenever you run valid?, which update_attributes does.
Example:
irb(main):001:0> album = Album.new
=> #<Album id: nil, name: nil, release_date: nil, rating: nil, genre_id: nil,
artist_id: nil, created_at: nil, updated_at: nil>
irb(main):004:0> album.errors.add :artist, "You've selected Justin Bieber (!!!)"
=> ["You've selected Justin Bieber (!!!)"]
irb(main):006:0> album.errors.messages
=> {:artist=>["You've selected Justin Bieber (!!!)"]}
irb(main):007:0> album.valid?
=> true
irb(main):008:0> album.errors.messages
=> {}
Don't abuse setters, use proper validations. For example (not tested):
require 'chronic'
class Bill < ActiveRecord::Base
validate :service_date_validation
def service_date_text
service_date.try(:strftime, "%m/%d/%Y")
end
def service_date_text=(date)
if date.present?
if Chronic.parse(date)
self.service_date = Chronic.parse(date)
else
self.service_date = false
end
else
self.service_date = ''
end
end
private
def service_date_validation
if self.service_date == false
self.errors.add(:service_date_text, "invalid date format hello.")
end
end
end
... There are also some gems which provide date validations, such as:
https://rubygems.org/gems/validates_timeliness
https://rubygems.org/gems/date_validator
https://rubygems.org/gems/rails_validations (disclaimer: I am the author)
... as well as some others...
I'll bet the issue is that Rails doesn't expect setter methods to add errors. I would make service_date_text just an attr_accessor and then call a validate method which sets service_date or adds an error.
attr_accessor :service_date_text
validate :service_date_text_format
private
def service_date_text_format
return unless service_date_text # or self.service_date ||= '' and then return
if date = Chronic.parse(date)
self.service_date = date
else
errors.add(:service_date, 'invalid format')
end
end

Solution to AbstractController::DoubleRenderError

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

Resources