Sort by date span - ruby-on-rails

Let's say we have the following model.
create_table :meetings do |t|
t.datetime :started_at
t.datetime: ended_at
end
class Meeting < ActiveRecord::base
end
How would I order a meetings_result, so that the longest meeting is the first meeting in the collection and the shortest meeting the last.
Something like
Meeting.order(longest(started_at..ended_at))
Obviously that doesn't work.
How would I achieve this, preferably without using raw SQL?

I don't think you can do it without using raw SQL.
Using Raw SQL:
Meeting.order('(ended_at - start_at) DESC')
(works with PostGreSQL)

No SQL? Two options come to mind. Create an array of hashes and sort it there, or add another column in the db and sort on that.
# How many records in the meetings table? This array of hashes could get huge.
meetings_array = []
Meeting.all.each do |meeting|
meetings_array << {id: meeting.id, started_at: meeting.started_at, ended_at: meeting.ended_at , duration: meeting.ended_at - meeting.started_at }
end
meetings_array.sort_by { |hsh| hsh[:duration] }
Or, create another column:
# Is it worth adding another column?
create_table :meetings do |t|
t.datetime :started_at
t.datetime :ended_at
t.datetime :duration
end
Update this column whenever you have both started_at and ended_at. Then you can:
Meeting.order("duration")

Related

How to query on a method value?

Consider this table:
create_table "liquor_lots", force: :cascade do |t|
t.integer "recipe_id"
t.datetime "created_at", precision: 6, null: false
t.integer "counter"
end
And the resulting model
class LiquorLot < ApplicationRecord
def lotcode
"#{recipe_id}#{created_at.strftime("%y")}#{created_at.strftime("%W")}#{created_at.strftime("%u")}"
end
def pallet_lotcode
"#{lotcode}-#{counter}"
end
end
I'd like to do the equivalent of this in SQL:
Select distinct(lotcode) from liquor_lots
I've tried this and it understandably fails because lotcode is not a column on the liquor_lots table. But I've always been advised against adding columns to store data that is derived from data in other columns.
So how do I search for those values?
For context, my lotcode actually consists of many more values concatenated together, I just limited to three in the example for readability.
As far as I know, with basic ActiveRecord you cannot do that.
ActiveRecord would have to know too much about your ruby code.
You could implement a SQL query that concatenates the relevant values by hand (see comment to your question).
Or you can query all objects (or just the relevant values using pluck()) and then work on that with standard Ruby Array/Enumerable methods (in memory). If the application is not performance-critical, happens rarely, and you do not have thousands of the liquor_lots, that would be an okay productivity-tradeoff in my eyes.
Besides storing it in an own column, you could also extract the codes in separate table and make PalletLotcode an entity of its own. LiquorLots would than belong_to a single PalletLotcode which would have_many LiquorLots. But compared to the separate column this is a rather complex operation, but makes sense if other information is to be stored on the Lotcodes.
You can try something like:
LiquorLot.where("recipe_id = :rcp_id AND created_at >= :begin_of_day AND created_at <= :end_of_day", {begin_of_day: calculate_begin_of_day, end_of_day: calculate_end_of_date, rcp_id: id})
calculate_begin_of_day and calculate_end_of_date can be implemented using Date.comercial method and Date.beginning_of_day and Date.end_of_day

rails4 pluck with order and limit

In my sidebar I display the freshly created user profiles. Profile belongs_to user and user has_one_profile. I realized that I only use 3 columns from the profile table so it would be better to use pluck. I also have a link_to user_path(profile.user) in the partial, so somehow I have to tell who the user is. At the moment I'm using includes, but I don't need the whole user table. So I use to many columns both from the user and the profile tables.
How can I optimize this with pluck? I tried a few versions, but always got some error (most of the time profile.user is not defined).
My current code:
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3) if user_signed_in?
end
create_table "profiles", force: :cascade do |t|
t.integer "user_id", null: false
t.string "first_name", null: false
t.string "last_name", null: false
t.string "company", null: false
t.string "job_title", null: false
t.string "phone_number"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar"
t.string "location"
end
Okay let's explain three different way to accomplish what you are looking for.
First of all there is a difference in includes and joins
Includes just eager load the association with all of the specified columns for associations. It does not allow you to query or select multiple columns from both table. It what joins do . It allow you to query both tables and select columns of your choice.
def set_sidebar_users
#profiles_sidebar = Profile.select("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id").joins(:user).order("profile.created_at desc").limit(3) if user_signed_in?
end
It will return you the Profiles relation which has all of the columns you provided in select clause. You can get them just like you do for profile object e-g
#profiles_sidebar.first.user_email will give you user email for this profile.
This approach is best if you want to query on multiple tables or wanna select multiple columns from both table.
2.Pluck
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3).pluck("users.email,profiles.first_name") if user_signed_in?
end
Pluck is just used to get columns from multiple associations but it does not allow you to use the power of ActiveRecord. It simply returns you the array of selected columns in same order.
like in the first example you can get the user for profile object with #profiles_sidebar.first.user But with pluck you cannot because it's just a plain array. So that's why your most of the solutions raise error profile.user is not defined
Association with selected columns.
Now this is option three. In first solution you can get multiple columns on both tables and use the power of ActiveRecord but it does not eager load the associations. So it will still cost you N+1 queries if you loop through the association on returned result like #profiles_sidebar.map(&:user)
So if you wanna use includes but want to use selected columns then you should have new association with selected columns and call that association.
e-g
In profile.rb
belongs_to :user_with_selected_column,select: "users.email,users.id"
Now you can include it in above code
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user_with_selected_column).limit(3) if user_signed_in?
end
Now this will eager load users but will select only email and id of user.
More information can be found on
ActiveRecord includes. Specify included columns
UPDATE
As you asked about the pros for pluck so let's explain it.
As you know pluck returns you the plain array. So it does not instantiate ActiveRecord object it simply returns you the data returned from database.
So pluck is best to use where you don't need ActiveRecord Objects but just to show the returned data in tabular form.
Select returns you the relations so you can further query on it or call the model methods on it's instances.
So if we summaries it we can say
pluck for model values, select for model objects
More informations can be found at http://gavinmiller.io/2013/getting-to-know-pluck-and-select/

Is there a way to pass property.method to .where() instead of property in Rails?

My Schedule model looks like this:
create_table "schedules", force: :cascade do |t|
t.integer "week_day"
t.time "opening_time"
t.time "closing_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "taco_place_id"
end
add_index "schedules", ["taco_place_id"], name: "index_schedules_on_taco_place_id"
As you can see, there are opening_time and closing_time properties and I have a realtionship Schedule belongs_to :taco_place and TacoPlace has_many :schedules, dependent: :destroy.
What I am trying to do from the Schedule model is to get the actual schedule for a TacoPlace for today (if it exists).
I have already implemented a scope for having today's schedules for a TacoPlace (depending on the week_day property) that looks like this:
scope :today_for_taco_place, ->(taco_place){where(taco_place_id: taco_place.id, week_day: Time.now.wday)}
and I'm using it in this method:
def self.actual_for_taco_place(taco_place)
today = self.today_for_taco_place(taco_place)
today.where("opening_time <= :now and closing_time >= :now", now: Time.now.utc).first
end
I have tested it and it "works". The thing is that if I run "Schedule.first.opening_time" on the console I get "2000-01-01 06:00:00 UTC". As you can see, it does not only include the time, but also the day (even if it was seeded as "opening_time: "15:00".to_time, closing_time: "24:00".to_time").
Finally, here is the question:
Is there a way that I can run something like this: (I know this won't work yet)
def self.actual_for_taco_place(taco_place)
today = self.today_for_taco_place(taco_place)
today.where("#{opening_time.strftime("%H%M")} <= :now and #{closing_time.strftime("%H%M") >= :now", now: Time.now.utc.strftime("%H%M")).first
end
So that the .where() method doesn't look for the property (opening_time or closing_time), but rather perform the strftime() method so I can compare the time only? Or should I save the opening_time and closing_time as integers (i.e. "1200") or manually convert them in a method?
Sorry if my question was long or hard to understand. Thank you in advance for your advise.
Opening_time and closing_time are now integers. I figured out that I don't gain anything from it being a "time" instead of an "integer" since it is only representing an hour.

convert string to datetime when making activerecord db query

I have the following table in my DB:
class CreateGoogleRecords < ActiveRecord::Migration
def change
create_table :google_records do |t|
t.string :user_id
t.string :date
t.text :stats
t.string :account_name
t.integer :total_conversions
t.decimal :total_cost
t.timestamps
end
end
end
I'm looking to create a table inside a view that groups together records by month (I can't use "date created because sometimes they are scraped in bulk from an API).
There is a lot of legacy code involved so rather than convert the column to datetime I was hoping I could convert the date string to a datetime object when performing the query.
I've tried writing a scope like:
scope :stats_for_reports, ->(start_date, end_date, user_ids) { select('user_id, sum(total_cost) as total_cost, sum(total_conversions) as total_conversions')
.where('date >= ? and date <= ?', start_date, end_date)
.where(user_id: user_ids)
.group(DateTime.parse(:date).month.to_s)}
but I receive a TypeError: can't convert Symbol into String error.
In the console I've been trying things like:
GoogleRecord.where(date: date_start..date_end).group{ |m| DateTime.parse(m.date).month }
or
GoogleRecord.where(date: date_start..date_end).group(:date).to_date
Am I on the right track with any of these?
Have you considered using ActiveRecord before_save, after_save, after_initialize? You may be able to create a DateWrapper (very similar to the EncryptionWrapper below) and convert the string to a date transparent to the rest of the code.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Looking for best way to retrieve business hours from database

I'm using Ruby on Rails and I'm storing business hours like this:
CREATE TABLE "business_hours" (
"id" integer NOT NULL PRIMARY KEY,
"business_id" integer NOT NULL FOREIGN KEY REFERENCES "businesses",
"day" integer NOT NULL,
"open_time" time,
"close_time" time)
(which came from the thread at:
Storing Business Hours in a Database )
Now I want to pull the hours out for each day of the week and display them, and I'm trying to find the best (or at least a good) way.
Should I just have a helper method that loops through getting the days (from 0..6) for a given business_id and assign it to a variable for the associated day? I feel like there must be a better way -- with an array, or something, but it's hurting my head thinking about it, because I also have a form of 'select's where any of the hours for a given business can be updated at once.
Thanks for any guidance!
Use the enum column plugin to declare the day field as a enum field.
class BusinessHours < ActiveRecord::Migration
def self.up
create_table :business_hours do |t|
t.integer :business_id, :null => false
t.enum :day, :limit =>[:sun, :mon, :tue, :wed, :thu, :fri, :sat], :nill => false
t.time :open_time, :null => false
t.time :close_time, :null => false
end
end
def self.down
drop_table :business_hours
end
end
Now when you do find on the BusinessHour model you will get the day as a string.
b = BusinessHour.find_by_business_id(2).first
p b.day.to_s.camelize #prints Sun/Mon/Tue etc.
You can use the enum_select and enum_radio form helpers to create list box/radio button group for the enum group:
Since the number of days in a week really is fixed, you can join the table 6 times (plus the original) and do a query for a single row. I'd probably just do a single query and loop through the rows though.
Have you considered serializing the business hours? Using serialization you are essentially storing objects in the database.
class BusinessHour < ActiveRecord::Base
serialize :hours
...
end
BusinessHour.create :business => #business, :hours =>
{:mon => [mon_start_time, mon_end_time], :wed => [wed_start_time, wed_end_time],
...}
Personally I would go with the bitwise approach described in linked question. All you really need to do to make it work is write new accessor methods.
It would be easier to find the business and use the associations to retrieve the business_hours rows.
Try this in your view
<% #business.business_hours.each do |hrs| %>
<%= hrs.day_name %>: Open-<%= hrs.open_time %> Close-<%= hrs.close_time %>
<%- end -%>
In your business_hour.rb model file, create a default scope to make sure the days are always listed in order. You can also create the day_name method to make it easier to display the day.
default_scope :order => 'day ASC'
def day_name
case self.day
when 0 then "Sun"
when 1 then "Mon"
...
end
end

Resources