Efficient way of storig time intervals - ruby-on-rails

In my Rails application i have a Shop model.
I need to store weekdays and corresponding opening hours like:
Monday 18:00 - 2:00
Tuesday 19:00 - 1:00
etc..
I expect to have a lot of shop records and i'm wondering what is the best way to store it provided that i care a lot about quick access.
One way is to make use of Postgresql hstore type.
I would keep a hash like "Monday" => "18:00-2:00", "Tuesday"=>...
in an oppening_hours column in a shop table
Or I can solve it with a simple relational approach
class Schedule < ApplicationRecord
#weekday:string, start_at:datetime, end_at:datetime
belongs_to :shop
end
class Shop < ApplicationRecord
has_one :schedule
#...
end
But then, any time i need to show info about shop i have to query 2 tables.
I am not sure if this is the optimized solution when talking about big table that is about to grow rapidly.
I would appreciate some advice.

You may use the native ARRAY type. It is storage-efficient and will probably have decent support in ORMs.
https://www.postgresql.org/docs/9.4/static/arrays.html
I would consider something like:
CREATE TABLE shop (
...
schedule text[][3]
);
INSERT INTO shop
VALUES (...
'{{"Monday", "18:00", "2:00"}, {"Tuesday", ...}}');

I would use your second approach but with some differences
class Schedule < ApplicationRecord
#weekday:integer, start_at:datetime, end_at:datetime
belongs_to :shop
end
class Shop < ApplicationRecord
has_many :schedule
#...
end
Here some consideration about this solution:
First of all, I think it is suitable to store the start and end date in different columns. The reason is that you will be able to perform queries like "give me all shops which are open now" you will not have to parse each value in the column "opening_interval".
I would use a number to store the day of the week. It seems intuitive that, when querying the database, it is easier to compare a column with a number than comparing it to a string.
I think your relationship should be one-to-many in order to save up to 7 schedules for each shop.
I would add an ActiveRecord validation to check that the weekday cannot be repeated for the same shop (a shop should not have two different opening intervals for the same day)

Related

Rails: "Caching" the average of an associated model's values

In Rails, I have some models that look like this:
class Product
has_many :listings
end
class Listing
belongs_to :product
# quantity_in_kg
# total_price
# price_per_kg = total_price / quantity_in_kg
end
I'd like to be able to compare the listings for a product based on the price per kilogram, compared to the price per kilogram for the product. For example, this listing is only $2 per kilogram, whereas the product's average is $3.
Eventually, I'd like to be able to run a query that says "give me all of the listings which are below the average price of their product".
What's an effective way of doing this? I was thinking of something custom with ActiveRecord callbacks, and caching the per-kilo average in the products table, and the per-kilo price for each listing in the listings table. There's probably a lot of scope for getting that wrong, so I was wondering if there was another way.
I'm using Postgres 9.6 and Rails 5.1.0.
(Bonus points: listings can also be active/inactive, and I'd only like to compare the average of all active listings).
My suggestion is to start with a simple after_save callback and see where it takes you. You can add some updating criteria like "only recalculate on create/destroy or if active has been updated", add some transactions if you're feeling extra careful, etc..
If gets too slow, add a background worker to update it regularly (for example).
class Listing
after_save do
product.update(
avg_price_per_kg: product.listings.where(active: true).average(:price_per_kg)
)
end
end

Should I use a LIKE query for these ActiveRecord relationships?

Let's say I have a single web page form user interface with 2 sets of checkboxes. With set 1 checkboxes, I can check off what Trainers I would like ("Jason", "Alexandra, etc.) With set 2 checkboxes, I can check off what animals I would like to see ("Tigers", "Bears", etc.) Once I submit the form with these options, I get back a list of zoos that match the criteria (let's assume all the trainers work at all the zoos and all the animals are at all the zoos for discussion's sake)
We'll be running our database query by "name" (e.g., search using trainer names and animal names, NOT database ids)
Let's say we are using a Postgres database that has hundreds of thousands of rows (if not millions).
Is it more efficient to search using an "ILIKE" query or is it better to do a standard join query (e.g., Zoo.includes(:animals, :trainers).where("animals.name = ? and trainers.name = ?", animal_names, trainer_names)?
Is there a better way than what I just showed in #1 above?
model setup
class Zoo < ActiveRecord::Base
has_many :animals, through: zoo_animals
has_many :trainers, through: zoo_trainers
has_many :zoo_trainers
has_many :zoo_animals
end
class Animal < ActiveRecord::Base
has_many :zoos, through :zoo_animals
has_many :zoo_animals
end
class Trainer < ActiveRecord::Base
has_many :zoos, through :zoo_trainers
has_many :zoo_trainers
end
class ZooAnimal < ActiveRecord::Base
belongs_to :animal
belongs_to :zoo
end
class ZooTrainer < ActiveRecord::Base
belongs_to :zoo
belongs_to :trainer
end
EDIT: let's suppose I don't have access to the database ID's.
LIKE '%Jason%' is much less efficient than querying for the exact string 'Jason' (or querying for an ID), because while exact comparisons and some uses of LIKE can use an index on the column being queried, LIKE with a pattern beginning with a wildcard can't use an index.
However, performance doesn't sound like the most important consideration here. LIKE %Jason% will still probably be fast enough on a reasonably sized database under reasonable load. If the application really needs to search for things by substring (which implies that a search might have multiple results), that requirement can't be met by simple equality.
There are an endless number of higher-powered solutions to searching text, including Postgres built-in full-text search and external solutions like Elasticsearch. Without specific requirements for scaling I'd go with LIKE until it started to slow down and only then invest in something more complicated.

How to populate rails table with data from other tables?

I'm a bit of a noob programmer so apologies if the question isn't clear enough.
I'm trying to create a basic rails app where I have 3 different tables: usages(month, usage), prices(month, price) and spends(month, spend).
I'm trying to get it so that spend = usages.usage * prices.price. I've put the following code into my Spend model:
class Spend < ActiveRecord::Base
c = Usage.all.count
i = 1
while i <= c
u = Usage.find(i)
p = Price.find(i)
Spend.create(month:u.month, spend:u.usage*p.price)
i += 1
end
end
This works great initially, but as soon as I start adding and removing usages and prices, their id's change so it isn't as clear cut. How can I do this in a much better way?
Thanks,
Kev
In this case, I would lean against making a separate Spend model, since all it does is calculate data that is already present in the database. Unless you have severe caching requirements (and I doubt it in your case), you can use simple instance methods to retrieve the data you want.
First figure out how your Usage and Price models are related. Since you seem to be associating them by id, it appears to be a one-to-one relationship (correct me if I'm wrong on this). However, associating by assuming they have the same primary key is a dangerous approach - rather have one model point to the other using a foreign key. We'll pick the Price model to hold a primary key for Usage, but the reverse can also work. You'll need to add a column using a migration like this:
def change
add_column :prices, :usage_id, :integer
end
Your models should then look like this:
class Usage < ActiveRecord::Base
has_one :price
def spend
usage * price.price
end
end
class Price < ActiveRecord::Base
belongs_to :usage
end
And you can find your spend value for an individual usage item like this:
usage = Usage.find(some_id)
puts usage.spend
Or you can get multiple 'spends' like this:
Usage.include(:price).each do |usage|
puts usage.spend
end
I've left out any reference to month, as I'm not sure how you are using it or if it's needed at all for calculating spend.
Have a look at the Active Record association guide: http://guides.rubyonrails.org/association_basics.html

Rails - complicated query

I am new to rails, and Im having hard time writing a query using rails' methods.
What I am trying to accomplish is to have a Canteen model that has many Meals, however I want to only display meals that are being served that day.
So I created a Canteen model, that has_many Meals.
Then I created Served_date model, that belongs_to meal and Meal has_many Served_dates so we can specify multiple dates when the meal is being served.
How would I make query like this?
//P.S.: Served_date.where(served_at: Date.today) returns Served_dates that are being served today
Thanks !
Firstly, the model name should not be underscored by convention, hence you should use ServedDate instead of Served_date.
This query should give you what you need:
Meal.joins(:served_dates).where(served_dates: {served_at: Date.today})
Better yet, you can parameterize it and turn it into a scope to be more portable:
class Meal < ActiveRecord::Base
scope :served_on, lambda{|date| joins(:served_dates).where(served_dates: {served_at: date}) }
end
And then call:
Meal.served_on(Date.today)

Rails 3 Associations for Traffic Data without Foreign Key

I have to define an association that doesn't seem to fit in well to the "has_one / belongs_to" bucket very well.
The situation is this, I have a table whereby each row corresponds to monthly statistics for a given month and year. I'd love to be able to define certain associations on my model such as record.prior_month or record.prior_year which would correspond to the prior month / year of the current record.
I can't think of any clever way to do this as it doesn't make any sense to maintain foreign keys that would have to be updated every month for tons of records.
I can always handle the logic in the controller, but I'd prefer to keep it with the model if I could.
Thanks!
Mike
So rather than store the Month/Year, also store the Month+Year*12. So March 2011 is 24135
That way, you know the next month is 21436, and you can easily paginate over your records.
TrafficGroup.order("month_calculated").paginate(:page=>params[:page])
Something like this?
class MyModel < AR::Base
def prior_month
created_at.month
end
def prior_year
created_at.year
end
end
example = MyModel.last
example.prior_year
#=> 2010
example.prior_month
#=> 3
You can do this a few ways. Assuming the month is stored in the model.
My favorite is scopes, since it plays nicely with other associations.
For instance you can do:
class TrafficRecord < ActiveRecord::Base
scope :only_month, lambda {|month| where :month => month} # this should also contain join conditions
def prior_month
self.class.only_month(self.month - 1) #decrement your month
end
end

Resources