Compiling counts of records by month and user - ruby-on-rails

I have a dashboard I'm working on that basically lists all the users of the system and the number of activities they have in each month. I'm curious on what others think would be an efficient way to compile this data. I'm thinking I should eventually attempt to get it in this format:
{
user1 => { :jan => 13, :feb => 21, :mar => 4, ... },
user2 => { :jan => 16, :feb => 18.... },
....
}
Though I'm by no means married to that idea. Here is screenshot of what I'm working towards to help give you a better idea:
http://i.stack.imgur.com/HbSln.png

If you're using an activity log to track activity (one record per activity), then it should be pretty simple. Basically, COUNT(*) your activity records per user, and GROUP them by the month in which they occurred.
Alternatively, and depending on your requirements, you could also just have a single record per user, per month (instead of a log), with an incrementing count of the number of activities.

Related

How to count bookings if allowing for efficient overlap

I've looked over some SO discussions here and, well at least I haven't seen this perspective. I'm trying to write code to count bookings of a given resource, where I want to find the MINIMUM number of resources I need to fulfill all bookings.
Let's use an example of hotel rooms. Given that I have the following bookings
Chris: July 4-July 17
Pat: July 15-July 19
Taylor: July 10-July 11
Chris calls and would like to add some room(s) to their reservation for friends, and wonders how many rooms I have available.
Rooms_available = Rooms_in_hotel - Rooms_booked
The Rooms_booked is where I'm having trouble. It seems like most questions (and indeed my code) just looks at overlapping dates. So it would do something like this:
Booking.where("booking_end >= :start_check AND booking_start <= :end_check", { start_check: "July 4, 2021".to_date, end_check: "July 7, 2021".to_date})
This code would return 3. Which means that if the hotel theoretically had 5 rooms, I would tell Chris that there were 2 more rooms left available.
However, while this method of counting is technically accurate, it misses the possibility of an efficient overlap. Namely that since Taylor checks out 4 days before Pat, they can both be "assigned" the same room. So technically, I can offer 3 more rooms to Chris.
So my question is how do I more accurately calculate Rooms_booked allowing for efficient overlap (i.e., efficient resource allocation)? Is there a query using ActiveRecord or what calculation do I impose on top of the existing query?
i don't think just only a query could solve your problem (or very very complex query).
my idea is group (and count) by (booking_start, booking_end) in order booking_start asc then reassign, e.g. if there're 2 bookings July 15-July 19 and 3 bookings July 10-July 11 then we only could re-assign for 2 pairs, and we need 3 rooms (2 rooms for July 15-July 19-July 10-July 11 and 1 for July 10-July 11.
and re-assign in code not query (we can optimize by pick a narrow range of time)
# when Chris calls and would like to add some room(s) to their reservation for friends,
# and wonders how many available rooms.
# pick (start_time, end_time) so that the range time long enough that
# including those booking {n} month ago
# but no need too long or all the time
scope :booking_available_count, -> (start_time, end_time) {
group_by_time = \
Booking.where("booking_start >= ? AND booking_end <= ?", start_time, end_time)
.group(:booking_start, :booking_end).order(:booking_start).count
# result: {[booking_start, booking_end] => 1, [booking_start, booking_end] => 2, ... }
# in order booking_start ASC
# then we could re-assign from left to right as below
booked = 0
group_by_time.each do |(start_time, end_time), count|
group_by_time.each do |(assign_start_time, assign_end_time), assign_count|
next if end_time > assign_start_time
count -= assign_count # re-assign
break if count <= 0
end
booked += count if count > 0
end
# return number of available rooms
# allow negative number
Room.count - booked
}

Rails - Create order and order-rows with REST api

I just have a question regarding how to implement some logic.
Im building a API that allows the client to create orders.
This is solved by a OrderController#create so no problem!
Now, the issue is that an order can have many order-rows, all the relations are set correct but where should i create the order-rows in for the order?
Should the OrderController handle this or should i have a new controller that creates the order-rows for the particular order?
The clients post is sending the following json-data:
{
"status": "paid",
"total_sum": 20,
"payment": "card",
"order_rows": [
{
"id": 12,
},
{
"id":13
}
]
}
I ran into something similar with a project I'm working on now. The best (and long term simplest) solution was definitely to make a whole new model/controller.
*Order
status (should be an int or enum probably)
total (should loop through all order rows and total)
payment (should be an int or enum probably)
has_many order_rows
**OrderRow
belongs_to Order
item_sku
item_name
item_descr
item_cost
etc. etc.
This allows you to easily search for not just items, but orders that include items by name or sku, orders that include items by description.
Your totals are dynamic.
You can retrieve total order numbers or movement numbers on a per item basis.
It is so much easier to create and update orders.
The benefits go on.
It can easily be scoped;
scope :this_orders_rows, -> (order_id) {where(order_id: order_id)}
And it saves you from having to parse through hashes and arrays everytime.
To get technical about it, your order_controller should control ONLY your orders. If you start adding in a heap of other code to read through the arrays its going to get VERY cluttered. It's always better to move that to some other area.

ActiveRecord query for unique on a given column, given a certain order

I have Events, with a date, and a name, that has_many Minutes
I need to find all the events that are the first one by date to have the given name, then collect all of the minutes associated with those events.
Event.order(date: :asc).uniq_by(&:name).map(&:minutes).flatten
almost works. except that I end up with an array, and not an ActiveRecord::Relation, and that I'd really like to start on Minute and make this into a scope like... Minute.first_time_events() gives me all the minutes from events that were the first event by date with their name.
One final caveat, I'm using rails 3.2. Thanks
If you don't mind having raw sql in your app you could do it all in one call with
Minute.where(event_id: Event.joins(
"INNER JOIN (SELECT url, MIN(date) AS minDate FROM events GROUP BY name) groupedEvents ON events.name = groupedEvents.name AND events.date = groupedEvents.minDate"
))
If you don't mind making two calls to the db then I would definitely go with the readability of Max's suggestion
ids = Event.order(date: :asc).uniq_by(&:name).map(&:id)
minutes = Minute.where(event_id: ids)
I would pull the ids of the events and then use that in a scope to fetch Minutes.
ids = Event.uniq(:name).order(date: :asc).ids
minutes = Minute.joins(:event).where(events: { id: ids })
The caveat here is that this is how I would do it in Rails 4. Some backporting may be necessary but you should really be focusing on updating your app since 3.2 will be EOL any day now.

my active record query causing slow loading times

I have a sidebar on my app where I list customers with "open" tickets. When there are a lot of customers with open tickets, my site runs really slow. Upon examining the logs, it seems the lag is coming from the sidebar. Here's the query:
#sidebar_customers = current_user.company.customers.open_or_claimed.where("user_id = ? OR user_id IS NULL", current_user.id).order(aasm_state: :asc)
and this is how I display the sidebar:
- #sidebar_customers.each do |customer|
%li#sidebar-customer{:class => "#{active_state_class(customer)}"}
= link_to raw("<i class = 'menu-icon fa #{claimed_class(customer)}' id = 'icon-cust-#{customer.id}'></i><span class = 'mm-text'>#{customer.full_name}</span>#{'<span id = "open" class = "label label-success pull-right">open</span>' if customer.open?}#{"<span id = 'new-cust-#{customer.id}' class = 'label label-danger'>new</span>" if customer.not_viewed_count > 0 && customer.claimed?}"), customer, id: "cust-#{customer.id}"
how can I make this faster? am i doing something wrong?
EDIT: also, this seems to be making the site slow as well:
#messages = current_user.company.messages.find(:all, :order => "id desc", :limit => 25)
This is not surprising since current_user.company.customers does SQL joins on the users table, the companys table and the customers table. And SQL joins are expensive (time consuming). As you noticed the performance of the joins decreases significantly with increase in data (customers in this case).
There are many ways to optimize the performance here.
First experiment with eager loading of data. See the section on Eager Loading Multiple Associations
http://guides.rubyonrails.org/active_record_querying.html
Next review this SO thread on differences between includes and Joins on performance.
Rails :include vs. :joins
Be sure to review the SQL generated in the log file as you experiment. See if you can eliminate any unwanted joining.
Other usual things to speed up queries: Add indexes to the tables. Reduce the data returned by adding more constraints (where conditions).

rails categorizing nested resources

I have users with nested resources posts. I have a page that shows all posts from all users. However, on the left column I want to categorize them by user, as follows, with links to a URL with a parameter like ?username=laura, for example, to show only their posts. I'm pretty sure I can figure out the URL params, but getting them sorted, as shown below, is difficult for me. Any suggestions? Thanks!
Adam (23)
Brad (12)
Mike (1)
Users.includes(:posts).map{|u| [u.name, u.posts.count, u.id]}.sort
Will give you a sorted array of users, post counts, and user id like
[
["Adam", 23, 23],
["Brad", 12, 2],
["Mike", 1, 44]
]
You can this user the first two elements for your link and the user id for the link destination.
This might get slow with thousands of users and/or posts, but it should get you started.
You should sort them:
#sorted_users = users.sort { |left,right| left.posts.count <=> right.posts.count }
Also, if you have a cache_column for posts, you should probably use the database to implement this.

Resources