How to best work with bulk data json render in rails? - ruby-on-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

Related

Rails - How to generate a single Json with these 2 tables

I have 2 tables, order and product_orders
they are related, in product_order it has the order_id
when I render the Json "render json: order", it comes out correct,
if I do "render json: p_order", it also outputs correct.
But I needed to generate the order json, with the items "quantity, unity_price and total from the Product_order table, how would I do that?
any suggestions or links for understanding/study?
def call_command
orders = Order.all
product_orders = ProductOrder.all
order = orders.map do |t|
build = { order_id: "#{t.id}",
created_at_id: "#{t.created_at}",
user_id: "#{t.user_id}",
desk_id: "#{t.desk_id}",
status: "#{t.status}",
subtotal: "#{t.subtotal}",
total: "#{t.total}"
}
end
p_order = product_orders.map do |p|
build = { #product_order_id: "#{p.id}",
# created_at_id: "#{p.created_at}",
order_id: "#{p.order_id}",
product_it: "#{p.product_id}",
quantity: "#{p.quantity}",
unity_price: "#{p.unit_price}",
total: "#{p.total}"
}
end
render json: order
end
Assuming that your Order and ProductOrder models are setup like this:
class Order < ActiveRecord::Base
has_many :product_orders
end
class ProductOrder < ActiveRecord::Base
belongs_to :order
end
You can use the includes in your render call, like the following:
#orders = Order.all
render json: #orders, include: ['product_orders']
You can also use the ActiveModel Serializers as #Benjamin suggested:
#orders.as_json(include: :product_orders)

Rails 5 manage result from monads

I've got Rails 5 app with dry-monads on board. Monads are used to create the Appointment object inside create action in AppointmentsController. They return Success or Failure in the last step with below structure:
# services/appointments/create.rb
(...)
def call
Success(appointment_params: appointment_params)
(...)
.bind(method(:save_appointment))
end
private
def save_appointment(appointment)
if appointment.save
Success(appointment)
else
Failure(failure_appointments: appointment, appointments_errors: appointment.errors.full_messages)
end
end
After each action (success or failure) I want to send an email and display the corresponding json in AppointmentsController:
class Api::AppointmentsController < ApplicationController
def create
succeeded_appointments = []
failure_appointments = []
appointments_errors = []
batch_create_appointments_params[:_json].each do |appointment_params|
appointment = ::Appointments::Create.new(appointment_params).call
if appointment.success?
succeeded_appointments << appointment.value!
else
failure_appointments << appointment.failure[:failure_appointments] &&
appointments_errors << appointment.failure[:appointments_errors]
end
end
if failure_appointments.any?
AppointmentMailer.failed_mail(email, failure_appointments.size, appointments_errors).deliver_now
render json: {
error: appointments_errors.join(', '),
}, status: :bad_request
elsif succeeded_appointments.any?
AppointmentMailer.success_mail(email, succeeded_appointments.size).deliver_now
render json: {
success: succeeded_appointments.map do |appointment|
appointment.as_json(include: %i[car customer work_orders])
end,
}
end
end
I wonder if there is a better, faster way to record these errors than declaring 3 different empty arrays (succeeded_appointments, failure_appointments, appointments_errors) like at the beginning of create action? so far the create action looks heavy.
Create a separate service object for bulk creation:
# services/appointments/bulk_create.rb
class Appointments::BulkCreate
def initialize(bulk_params)
#bulk_params = bulk_params
end
def call
if failed_results.any?
AppointmentMailer.failed_mail(email, failed_results_errors.size, failed_results_errors).deliver_now
Failure(failed_results_errors.join(', '))
else
AppointmentMailer.success_mail(email, success_appointments.size).deliver_now
Success(success_appointments)
end
end
private
attr_reader :bulk_params
def failed_results
results.select(&:failure?)
end
def success_results
results.select(&:success?)
end
def success_appointments
#success_appointments ||= success_results.map do |appointment|
appointment.as_json(include: %i[car customer work_orders])
end
end
def failed_results_errors
#failed_results_errors ||= failed_results.map do |failed_result|
failed_result.failure[:appointments_errors]
end
end
def results
#results ||= bulk_params.map do |appointment_params|
::Appointments::Create.new(appointment_params).call
end
end
end
Then your controller will look like this:
class Api::AppointmentsController < ApplicationController
def create
result = ::Appointments::BulkCreate.new(batch_create_appointments_params[:_json]).call
if result.success?
render json: { success: result.value! }, status: :ok
else
render json: { error: result.failure }, status: :bad_request
end
end
end

Rails API Serializer

I want to combine 2 models and sort them out and expose the result on my serializer.
so in my user controller I have
#users_controller.rb
def transactions
#debit = current_user.debits.all
#credit = current_user.credits.all
#history = [#debit, #credit].flatten
#sorted_history = #history.sort_by { |item| item.created_at.strftime('%m/%d/%y') }
render json: UserTransactionSerializer.new(#sorted_history).call
end
and on my serializer I have:
module Api
module V1
class UserTransactionSerializer < BaseSerializer
def call
{
id: object.id,
amount: object.amount,
status: object.status,
created_at: object.created_at.iso8601
}
end
end
end
end
I'm getting this error bellow:
NoMethodError (undefined method `id' for #<Array:0x0055ce49b57b20>)
EDIT: Typo
Your #sorted_history is an array, but UserTransactionSerializer should be passed in an object.
So easy solution would be:
render json: #sorted_history.map{ |trans| UserTransactionSerializer.new(trans).call }
object is an Array, so you have to iterate and return hash for each object in that Array.
def call
object.map do |obj|
{
id: obj.id,
amount: obj.amount,
status: obj.status,
created_at: obj.created_at.iso8601
}
end
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!

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