Sum / grouping with date constraints? - ruby-on-rails

i've written the following sum/group methods in my 'StatementSales' model and want to be able to constrain the results by date, at the moment it's just producing totals for all valid db entries. In my views I want to provide links to 'One Week, One Month, Three Months, One Year' etc and ideally pass these to the methods below. How should I approach this?
def self.total_units
sum(:units)
end
def self.units_by_store
group(:store).sum(:units)
end
def self.units_by_territory
group(:territory).sum(:units)
end
def self.units_by_upc
group(:upc).sum(:units)
end
Many thanks in advance!

You could use scopes
Add this to your class
scope :between_dates, lambda { |start_date, end_date| where("date < #{end_date} AND date >= #{start_date}") }
scope :one_week, between_dates(Date.today, Date.today + 7.days)
Then you can do
def self.total_units
self.one_week.sum(:units)
end

Related

What is the best way to reuse a scope in Rails?

I'm confused to reuse or writing a new scope.
for example,
one of my methods will return future subscription or current subscription or sidekiq created subscriptions.
as scopes will look like:
scope :current_subscription, lambda {
where('(? between from_date and to_date) and (? between from_time and to_time)', Time.now, Time.now)
}
scope :sidekiq_created_subscription, lambda {
where.not(id: current_subscription).where("(meta->'special_sub_enqueued_at') is not null")
}
scope :future_subscription, lambda {
where.not(id: current_subscription).where("(meta->'special_sub_enqueued_at') is null")
}
so these were used for separate purposes in different methods, so for me what I tried is to check whether a particular account record will come under which of three subscriptions.
so I tried like:
def find_account_status
accounts = User.accounts
name = 'future' if accounts.future_subscription.where(id: #account.id).any?
name = 'ongoing' if accounts.current_subscription.where(id: #account.id).any?
name = 'sidekiq' if accounts.sidekiq_enqued_subscription.where(id: #account.id).any?
return name
end
so here what my doubt is, whether using like this is a good way, as here we will be fetching the records based on the particular subscriptions and then we are checking whether ours is there or not.
can anyone suggest any better way to achieve this?
Firstly, you are over using the scopes here.
The method #find_account_status will execute around 4 Queries as below:
Q1 => accounts = User.accounts
Q2 => accounts.future_subscription
Q3 => accounts.current_subscription
Q4 => accounts.sidekiq_enqued_subscription
Your functionality can be achived by simply using the #account object which is already present in memory as below:
Add below instance methods in the model:
def current_subscription?
# Here I think just from_time and to_time will do the work
# but I've added from_date and to_date as well based on the logic in the question
Time.now.between?(from_date, to_date) && Time.now.between?(from_time, to_time)
end
def future_subscription?
!current_subscription? && meta["special_sub_enqueued_at"].blank?
end
def sidekiq_future_subscription?
!current_subscription? && meta["special_sub_enqueued_at"].present?
end
#find_account_status can be refactored as below:
def find_account_status
if #account.current_subscription?
'ongoing'
elsif #account.future_subscription?
'future'
elsif #account.sidekiq_future_subscription?
'sidekiq'
end
end
Additionally, as far as I've understood the code, I think you should also handle a case wherein the from_date and to_date are past dates because if that is not handled, the status can be set based on the field meta["special_sub_enqueued_at"] which can provide incorrect status.
e.g. Let's say that the from_date in the account is set as 31st Dec 2021 and meta["special_sub_enqueued_at"] is false or nil.
In this case, #current_subscription? will return false but #future_subscription? will return true which is incorrect, and hence the case for past dates should be handled.

Rails 4: Sorting by 2 different scopes/class methods

I'm trying to sort my events by future events then by events that have already ended. I tried using 2 different scopes but I'm thinking now that I might need to use class methods instead. I'm just struggling on how, syntactically, I need to write these methods.
event.rb:
def active
self.event_date > Time.now
end
def inactive
self.event_date < Time.now
end
"event_date" is a datetime column in the events table.
events_controller.rb:
def index
#events = Event.all.sort_by {|a,b| [a.active, b.inactive]}
end
With this code, I'm getting an error: "undefined method `inactive' for nil:NilClass" but I've tried it several different ways and can't seem to figure out how to write it.
Your methods will be executed after running through the database and will be slow.
This can probably be improved:
Model
scope :active, -> { where('event_date >= ?', Time.now) }
scope :inactive, -> { where('event_date < ?', Time.now) }
Controller
#active_events = Event.active
#inactive_events = Event.inactive
#events = #active_events + #inactive_events

Fetching Data based date and time

I am trying to find results from today onwards but also want to include the yesterdays plans if the time is between 12:00am-5:00am
Right now i have the following
def self.current
where(
"plan_date >= :today",
today: Date.current,
)
end
Is there a way i can know the time of the day based on the users timezone which am setting as bellow in the app controller and make sure that if its before 6:am the next day i want to include the previous days results as well.
def set_time_zone(&block)
if current_user
Time.use_zone(current_user.time_zone_name, &block)
else
yield
end
end
Try this:
def self.current
where(
"plan_date >= :today",
today: (Time.zone.now.in_time_zone(get_user_time_zone) - 6.hours).beginning_of_day,
)
end
...where get_user_time_zone returns the time zone for the user (E.G.: America/New_York). I'm using - 6.hours because you wanted it to be "before 6am" local time.

ActiveRecord where method datetime passed

Thanks for your continuing support in my latest venture in a Rails app. It's been a while since I've made something in Rails so here is my latest question. I appreciate the help you've all given in the past.
I have a Model called Event.rb which contains a date:date and time:time fields for the Event.
I also have a method inside of the model which is..
def begins
DateTime.new(date.year, date.month, date.day, time.hour, time.min, time.sec)
end
As I can't see if something has truly passed because I only have Date and Time separate so I need them together.
My question is...
I want to be able to add in the DateTime :begins into the following other method in my Model...
def self.past
where("date <= ?", TIME_NOW)
end
Just like I have a method which is...
def upcoming?
self.date >= Time.now
end
Which I could easily change self.date to begins and would past I would imagine?
Thanks!
Perhaps something like this will work for querying the database for past events using your existing date and time columns:
scope :past, lambda {
where("date <= ? and time <= ?",
Time.now.strftime("%Y-%d-%m"),
Time.now.strftime("%H:%M:%S")
)
}
past_events = Event.past
For checking the current instance, you could continue to use your begins method:
def past?
begins < DateTime.now
end
#event = Event.first
#event.past?

Count records created within the last 7 days

How can I alter the query below to only select records created within the last 7 days?
self.favorites.count
This function is located in my User model.
def calculate_user_score
unless self.new_record?
self.score = (self.links.count * 5) + (self.favorites.count * 0.5)
end
end
You can add a where-condition like this:
self.favorites.where('created_at >= ?', 1.week.ago).count
And for your calculate_user_score method, you probably want to do that for links as well:
def calculate_user_score
unless new_record?
self.score = (links.where('created_at >= ?', 1.week.ago).count * 5) +
(favorites.where('created_at >= ?', 1.week.ago).count * 0.5)
end
end
I recommend you add a scope to your model:
class User < ActiveRecord::Base
scope :recents, where("created_at > ?", Time.now-7.days)
end
Then you can do
self.favorites.recents.count
In Rails 4+
This code seems not working:
"created_at > ?", Time.now-7.days
I tried like:
scope :recent, -> { where("DATE(created_at) > ?", (Date.today).to_time - 7.days) }
self.links.where("created_at > ?", Time.now-7.days).count
If you're working in Rails, you can just use the ago datetime methods, instead of doing weird time math.
scope :recent, -> { where("created_at > ?", 1.week.ago) }
In Rails, you can usually avoid a lot of the complicated data preparation and type-casting you might have to do in other languages / frameworks.
Re: the original post, I would probably refactor it like this:
# Using association extensions here to filter this down,
# the ellipses parenthetical should be whatever you're using for your
# association definition.
has_many :links ( ... ) do
def since(last_date)
where('created_at > ?', last_date)
end
end
has_many :favorites (...) do
def since(last_date)
where('created_at > ?', last_date)
end
end
# Don't use magic numbers; codify them for context.
LINK_SCORE_MULTIPLIER = 5
FAVE_SCORE_MULTIPLIER = 0.5
# Note this does not persist it in the database; if you want it to persist
# you'll want to execute an update instead. However it does memoize it so multiple
# calls will pull from the in-memory cache of the object instead of re-querying it
def score(recalculate: true)
#score ||= (links.since(1.week.ago).count * LINK_SCORE_MULTIPLIER) +
(favorites.since(1.week.ago).count * FAVE_SCORE_MULTIPLIER)
end
Then you just reference it passively:
#user.score # runs the query and saves to memory
#user.score # pulls from memory
#user.score(recalculate: true) # re-runs the query and saves to memory
#user.save # persists the result (assuming you have a field for :score)
It might require refactoring, but depending on how your data is modeled, you might be able to use a counter_cache to track it (this would require a has_many,through association, and the counter_cache would be on the joining model.
I was looking for records which could return last 7 days i.e. not including today. But this worked for me and it can work for last n days.
last_n_days = 7
Model.where('created_at BETWEEN ? AND ?', Date.today-last_n_days, Date.today-1).count
with scope
scope :last_n_days, lambda {|n| where('created_at BETWEEN ? AND ?', Date.today - n, Date.today - 1)}

Resources