Rails: error charting by time - ruby-on-rails

I am creating a chart based on account balance. And here is my some of my codes
module AccountsHelper
def products_chart_data
orders_by_day = Account.total_grouped_by_day(3.days.ago)
(3.days.ago.to_date..Date.today).map do |date|
{
created_at: date,
balance: orders_by_day[date].first.try(:total_balance) || 0
}
end
end
end
class Account < ActiveRecord::Base
belongs_to :user
has_many :books
def self.total_grouped_by_day(start)
balances = where(created_at: start.beginning_of_day..Time.zone.now)
balances = balances.group("date(created_at)")
balances = balances.select("created_at, balance as total_balance")
balances.group_by {|o| o.created_at.to_date }
end
end
My problems are:
1) I received an error undefined method `first' when mapping 3.days.ago, but successfully run the code when I change it to 2.days.ago. I understand that it is because I do not have data on 3 days ago as this account is new. My question is, how can I rescue this error, because I could have many other new accounts that do not have data yet, and what could I do to display result for 1 month, or 2 month?
Thanks in advance!

# ⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓
balance: orders_by_day[date].try(:first).try(:total_balance) || 0
try is the method, introduced by rails and defined on Object class, therefore it is defined on NilClass as well.
The implementation is quite straightforward: it checks whether receiver is empty and returns either the result of the call to it, or nil otherwise.

Related

Rails Query a List for a CRON Job

I'm a complete novice with CRON jobs but I think I have that set up correctly.
Ultimately what I'm trying to do is send an email every day at 8:00 am to users (and a couple others) that have not logged in within the last 3 days, have not received the email, AND are marked as active OR temp as a status.
So from querying the db in console I know that I can do:
first = User.where(status: 'active').or(User.where(status: 'temp'))
second = first.where("last_login_at < ? ", Time.now-3.days)
third = second.where(notified: false)
That's not certainly clean but I was struggling with finding a contained query that grabbed all that data. Is there a cleaner way to do this query?
I believe I have my cron job set up correctly using a runner. I have whenever installed and in my schedule.rb I have:
every 1.day, at: '8:00 am' do
runner 'ReminderMailer.agent_mailer.deliver'
end
So under app > mailer I created ReminderMailer
class ReminderMailer < ApplicationMailer
helper ReminderHelper
def agent_reminder(user)
#user = user
mail(to: email_recipients(user), subject: 'This is your reminder')
end
def email_recipients(agent)
email_address = ''
email_addresses += agent.notification_emails + ',' if agent.notification_emails
email_addresses += agent.manager
email_address += agent.email
end
end
Where I'm actually struggling is where I should put my queries to send to the mailer, which is why I built a ReminderHelper.
module ReminderHelper
def applicable_agents(user)
agent = []
first = User.where(status: 'active').or(User.where(status: 'temp'))
second = first.where("last_login_at < ? ", Time.now-3.days)
third = second.where(notified: false)
agent << third
return agent
end
end
EDIT: So I know I could in theory do a chain of where queries. There's gotta be a better way right?
So what I need help on is: do I have the right structure in place? Is there a cleaner way to query this data in ActiveRecord for the CRON job? Is there a way to test this?
Try combining them together as if understand the conditions correct
Have not logged in within the last 3 days,
Have not received the email
Are marked as active OR temp as a status
User.where("last_login_at < ? ", 3.days.ago).
where(notified: false).
where(status: ['active', temp])
module ReminderHelper
def applicable_agents(user)
User.where("last_login_at < ? ", 3.days.ago).
where(notified: false).
where(status: ['active', temp])
end
end
You don't need to add/ assign them to array. Because this relation is already like an array. You can use .to_a if you need array. If you just want to iterate over them then users.each should work fine.
Update
class User
scope :not_notified, -> { where(notified: false) }
scope :active_or_temp, -> { where(status: ['active', 'temmp']) }
scope :last_login_in, -> (default_days = 3) { where("last_login_at < ?", default_days.days.ago) }
end
and then use
User.not_notified.active_or_temp.last_login_in(3)
Instead of Time.now-3.days it's better to use 3.days.ago because it keeps time zone also in consideration and avoids unnecessary troubles and failing test cases.
Additionally you can create small small scopes and combine them. More read on scopes https://guides.rubyonrails.org/active_record_querying.html

Search by availability/dates Ruby On Rails

I've built a RoR app and implemented a simple booking system. The user is able to look for a space and can book it per day or per hour.
Everything works well, but I would now like to make the user able to look for a space depending on its availability.
I want to user to be able to select a start/end date and a start/end time and to show only spaces that don't have any booking included in this period.
I am using pg search at the moment to look for a space by category and location, but I have no idea how to implement a search by date and time, as it uses a different logic.
I've tried to do it by hand by creating an array of bookings for each space so I could compare it with the params, but it sounded very complicated and not clean (and I started being stuck anyway, as making it available for one hour or several hours or several days makes it even more complicated)
Is there a gem that could do this for me? If I have to do it by hand, what's the best way to begin?
Thanks a lot
Just create an instance method available? which tests there are no bookings that overlap the from to range. You can use none? on the relationship.
class Space
has_many :bookings
def available?(from, to)
bookings.where('start_booking <= ? AND end_booking >= ?', to, from).none?
end
end
Taking some inspiration from the answer of SteveTurczyn. The following might give you some inspiration.
class Space < ApplicationRecord
# attributes: id
has_many :bookings
def self.available(period)
bookings = Booking.overlap(period)
where.not(id: bookings.select(:space_id))
end
def available?(period)
if bookings.loaded?
bookings.none? { |booking| booking.overlap?(period) }
else
bookings.overlap(period).none?
end
end
end
class Booking < ApplicationRecord
# attributes: id, space_id, start, end
belongs_to :space
def self.overlap(period)
period = FormatConverters.to_period(period)
# lteq = less than or equal to, gteq = greater than or equal to
# Other methods available on attributes can be found here:
# https://www.rubydoc.info/gems/arel/Arel/Attributes/Attribute
where(arel_table[:start].lteq(period.end).and(arel_table[:end].gteq(period.start)))
end
def overlap?(period)
period = FormatConverters.to_period(period)
self.start <= period.end && self.end >= period.start
end
module FormatConverters
module_function
def to_period(obj)
return obj if obj.respond_to?(:start) && obj.respond_to?(:end)
obj..obj
end
end
end
With the above implemented you can query a single space if it is available during a period:
from = Time.new(2019, 10, 1, 9, 30)
to = Time.new(2019, 10, 5, 17, 30)
period = from..to
space.available?(period) # true/false
You can get all spaces available:
spaces = Space.available(period) # all available spaces during the period
Note that class methods will also be available on the scope chain:
spaces = Space.scope_01.scope_02.available(period)
I've also added the overlap scope and overlap? helper to simplify creating the above helpers.
Since in my version Booking has a start and end attribute (similar to Range) you can also provide it to any methods accepting a period.
booking_01.overlap?(booking_02) # true/false
To retrieve all bookings that that overlap this very moment:
bookings = Booking.overlap(Time.now) # all bookings overlapping the period
Hope this gave you some inspiration. If you'd like to know how the overlap checking works I have to forward you to this question.
Note: This answer assumes that the provided period is valid. A.k.a. start <= end. If you for some reason provide Time.new(2019, 10, 1)..Time.new(2019, 9, 23) the results are going to be skewed.

Rails 4 Create Related Object After Save

I have two models with the [fields]:
Order [:date]
Delivery Slot [:day]
Order belongs_to :delivery_slot
When an order is created, I want a delivery slot to be created with the :day set to the order :date.
So far I have created a new method create_delivery_slots in the Order controller that creates a Delivery Slot when the Order is created, but where I am stumped is, how do I get the Order :date in the Delivery Slot :day field?
#Create delivery slots if they dont already exist
def create_delivery_slots
existingslots = []
existingslots = DeliverySlot.all.select {|slot| slot.day == #order.date}
if existingslots.empty?
slot = DeliverySlot.new(:day => #order.date)
slot.save!
end
I have tried multiple approaches, but no luck. My gut tells me its something to do with strong parameters but I can't figure it out...
I'm not sure exactly of how you're set up but you'll probably want something like this:
class Order < ActiveRecord::Base
has_a :delivery_slot
after_create => :create_delivery_slots
.
#other code stuffs
.
.
private
def create_delivery_slots
existingslots = []
existingslots = DeliverySlot.all.select {|slot| slot.day == self.date}
if existingslots.empty?
slot = DeliverySlot.new(:day => self.date)
slot.save!
end
end
end
That's untested but it should be basically what you need.

Count Records within 30 days of today

I Have a model an Opportunity model that has an attribute called date_of_opportunity. I am trying to write a method that counts how many opportunities are within 30 days of today. However when I try to call my method in the console, I get the error 'undefined local variable or method'
Here is my model:
class Opportunity < ActiveRecord::Base
def calculate_num_days
num_days = 0
#opportunity = Opportunity.all
#opportunity.each do |opportunity|
if (opportunity.date_of_opportunity - Date.today < 30)
num_days = num_days + 1
return num_days
end
end
end
end
Can someone help me figure out whats wrong? Thanks!!
If you will get counts how many opportunities are within 30 days of today, you can try this :
class Opportunity < ActiveRecord::Base
def self.calculate_num_days(from = (Date.today-1.month).beginning_of_day,to = Date.today.end_of_day)
where(date_of_opportunity: from..to).count
end
end
And on your console you can type like this
Opportunity.calculate_num_days
Output looks like :
irb(main):001:0> Opportunity.calculate_num_days
←[0m←[1m←[35m (51.0ms)←[0m SELECT COUNT(*) FROM "opportunities" WHERE ("opportunities"."date_of_opportunity" BETWEEN '2014-05-04 00:00:00.000000' AND '2014-06-04 23:59:59.999999')
=> 2
You seem to want a class method, but are defining an instance method. Do this:
def self.calculate_num_days
...
end
Maybe, #opportunity = Opportunity.all should be #opportunities = Opportunity.all
Unless I am missing what you are trying to do I would let ActiveRecord do the heavy lifting. Opportunity.where("date_of_opportunity - :today < 30", today: Date.today).size
Disclaimer, this is completely untested

Rails variable returns two different values?

For some weird reason an instance variable I have puts out two different values on two different occasions.
$ puts #project.to_yaml
gives:
id: 3
title: '123'
created_at: 2014-04-07 23:54:18.253262000 Z
updated_at: 2014-04-09 09:20:33.847246000 Z
amount_donated: 50000
and
$ #project.amount_donated
gives:
nil
Explain this one to me because I'm terribly lost.
EDIT
Project model
class Project < ActiveRecord::Base
require 'date'
attr_accessor(:amount_donated)
before_save :convert_params
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
update_column(:amount_donated, value.to_i) shows that you have a column amount_donated, but attr_accessor :amount_donated shows that you have a virtual attribute. So which one is it?
I'd suggest removing attr_accessor :amount_donated
edit:
The attr_accessor :amount_donated does something like this:
class Project < ActiveRecord::Base
require 'date'
before_save :convert_params
def amound_donated
#amount_donated
end
def amound_donated=(value)
#amount_donated = value
end
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
Thus when you accessed #project.amount_donated you were actually accessing the getter method amount_donated not the column (ActiveRecord getter).
Seems that to_yaml saw the column instead of the ActiveRecord's getter.
Try this, might be you are using cached copy of #project
#project.reload.amount_donated

Resources