I have two models:
class Country
has_many :competitions
end
class Competition
belongs_to :country
end
Competition class has a position attribute. Admin can sort competitions by position. I want to sort countries with the minimum position of its competitions. I also want to joing competitions with country. How can i achieve that?
I want an output like:
X Country: (is at first order because Xcomp1's position is 1)
Xcomp1 (position: 1)
Xcomp2 (position: 12)
A Country:
Acomp1 (position:2)
Acomp2 (position:3)
Z Country: (is at last position because minimum position of its competitions are higher than other ones)
Zcomp1 (position:5)
I think you'd have to:
Country.order("(select min(position) from competitions where competitions.country_id = countries.id) asc")
I'm not sure how that syntax holds up across different RDBMSs -- should be good on PostgreSQL and Oracle
Related
The project I am working on has the following models:
Button: has many extra_prices (one for each Currency)
ExtraPrice: belongs to a product (in that case a Button) and has as an attribute a currency_id
Currency: there is a reference to a currency_id on an ExtraPrice as I mentioned above. FYI there are 4 currencies so far in the app.
Some of the buttons don't have an extra_price set in one of the currencies which causes an error in another part of the app.
I am trying to write a rake task that would:
- check all buttons missing an extra_price for one a more currencies
- find out which currency is missing
- create the extra price
So far I toyed with a few options but I am stuck (I am pretty junior as a dev, and especially on the back-end side/DB query).
I was thinking something like:
Button.transaction do
currencies = Currency.all.pluck :id
buttons_no_extra_price = Button.select { |button| button.extra_prices.length <
currencies.length }
end
and then I'm stuck :)
I would like to do something like
buttons_no_extra_price.group_by(|button| button.extra_prices.currency_ids)
(wrong formatting of course since extra_prices is an array and currency_id is an attribute on each extra_price)
but instead of grouping them by currency_id, I would like to group them by the missing currency_id or ids, maybe using the currencies variable above.
missing_prices = {currency1: [button1, button2], currency2: [button192, button208], currency3: [button392, button220]...}
This way I could loop through every Currency and create an extra_price on each button object of the nested array like:
missing_prices.each |currency, array_of_buttons| do
array_of_buttons.each do |button|
ExtraPrice.create!(currency: currency, product: button)
end
end
I am also thinking that from a performance standpoint it needs to be optimized so maybe work more with includes, joins, etc. but it's a bit above my current abilities to be totally honest.
So any help would be appreciated :)
Thanks!
So I think I follow your question, and if I am this should do the trick. Let me know if you have any questions. Note that there is probably a more performant way to do this, but given that it's a rake task performance won't need to be fully optimized unless you are dealing with millions of records.
all_currency_ids = Currency.all.pluck(:id)
Button.eager_load(:extra_price).group('buttons.id').having('count(extra_prices.id) < ?', all_currency_ids.count).each do |button|
missing_currency_ids = all_currency_ids - button.extra_prices.pluck(:currency_id)
missing_currency_ids.each do |missing_currency_id|
ExtraPrice.create!(currency: Currency.find_by(id: missing_currency_id), product: button)
end
end
Button.eager_load(:extra_price).group('buttons.id').having('count(extra_prices.id) < ?', all_currency_ids.count) is what gets you the buttons with missing extra prices. This hinges on the fact that each button has an extra price per currency, so I hope I interpreted that correctly.
(Note: I'm not 100% familiar with Rails 4, only 5 and 6. The underlying SQL principles remain the same regardless and are adaptable.)
You can do this in a single query if your relationships are set up correctly.
class Button < ApplicationRecord
has_many :extra_prices
has_many :currencies, through: :extra_prices
end
class ExtraPrice < ApplicationRecord
belongs_to :button
belongs_to :currency
end
class Currency < ApplicationRecord
has_many :extra_prices
end
I've added a relationship between Currency and ExtraPrice. This allows us to set up a relationship between Button and Currency using has_many :currencies, through: :extra_prices. Then we can get a Button's Currencies with button.currencies.
Now we can do a left join between Button and ExtraPrice and Currency. A left join, as opposed to the normal inner join, will pick up Buttons that have no ExtraPrices nor Currencies. We can't use Rails's left_joins, it will not do the right thing.
buttons_missing_currencies = Button
.includes(:currencies)
.joins("left join extra_prices ep on ep.button_id = buttons.id")
.joins("left join currencies c on c.id = ep.currency_id")
.having("count(c.id) < ?", Currency.count)
.group("buttons.id")
That will give you all the Buttons which lack a Currency in a single, efficient query. includes(:currencies) means each Button's Currencies will already be loaded avoiding making N+1 queries.
Now we can look through each button, discover which currencies are missing, and fill them in.
all_currencies = Currency.all
buttons_missing_currencies.each do |button|
missing_currencies = all_currencies - button.currencies
missing_currencies.each do |missing_currency|
button.extra_prices.create!(currency: missing_currency)
end
end
I have following relation, because same product can exist in multiple stores, so DB stores the same product record in database with different store_id for each store, I also stores the coords for each product, now my query returns duplicate products by title for each store.
For quick fix how can I modify the query to it returns the closest products only and group by the product title maybe. BTW I am using geocode gem and near function to select nearby product near([#lat, #long], 20, order: #sort.blank? ? 'distance' : false)
class Product < ActiveRecord::Base
belongs_to :store
end
class Store < ActiveRecord::Base
has_many :products
end
ID TITLE STORE_ID
1 product_1 1
2 product_1 2
3 product_1 3
4 product_2 1
5 product_2 2
6 product_2 3
I think you have a misconception in your model. If the same product can be in several stores, then A product does NOT belong to a store. Cause it could be in several.
You should change your associations to a has_and_belongs_to_many or a has_many through one. But if you really want this query done, you can use the group method in your query.
Like Product.near(...).group(:title)
You can also filter after in Rails. Assuming it's already an ordered set, something like
#products.to_a.uniq(&:title)
would work.
I have the following classes and relationships
City has_many Cinemas
Cinemas has_many Movies
Movies has_many Ratings
Movies Has_many Genres through GenreMovie
and I want to test queries like
* Show me the all movies in NewYork
* Show me the all movies in NewYork order by the rating
* Show me the all movies in NewYork order by length_of_movie, in genre "Action"
* show me all movies in Cinema "X" order by rating, that are in Genre "SciFi"
Currently the way I am doing as below, using factory girl, and chaining a bunch of models together to have data to check against,
city = create(:city)
cinema = create(:cinema, city: city)
5.times do
movie = create(:movie, cinema: cinema, tags: ["sci fi", "action"]
3.times do
create(:rating, score: 2.3, movie: movie)
end
end
and repeating that 3-4 to generate enough data to query against but it seems so clunky.
Is there a better way ?
I normally test this using a very "minimalistic" approach:
e.g. for your first case I would create two movies, one in NY, and one outside. Your method should only return the one in NY
For the second, create three movies, both in NY, with different rating. Create them in a not logic way, so that, no matter what, they will be sorted. Check whether your method returns them in the right order
Similar for the other cases.
I would not just create 5x3 movies. Makes no sense, and only costs time...
There are several factory_girl constructs you could use to clean these up. create_list will create an array of objects, cleaning up your x.times do blocks. A with_ratings trait on your movie factory could allow you to opt in to having ratings automatically created when you create a movie (via FactoryGirl callbacks). You could even have that use a transient attribute in order to control the number and rating. So your result could look something like this:
cinema = create(:cinema)
movies = create_list(
:movie,
5,
:with_ratings,
cinema: cinema,
tags: [...],
ratings_count: 3,
ratings_value: 2.3
)
If you need access to the city, you can get it via cinema.city.
See:
transient attributes
traits
Given
3 ActiveRecord models:
class Dealer < ActiveRecord::Base
end
class CarMake < ActiveRecord::Base
has_many :car_models
end
class CarModel < ActiveRecord::Base
belongs_to :car_make
end
non of either CarMake or CarModel should have additional foregin keys (making managing of makes/models isolated and independent),
adding join tables or associations is not prohibited and is welcome.
Problem
I need dealer to have assigned a desired subset of available car_makes and desired subset of car_models for each of respectively assigned car_make.
Example
Given this data:
car_models car_makes
------------------------ -------------
id car_make_id title id title
1 1 Flex 1 Ford
2 1 Fiesta 2 Chevrolet
3 1 Focus 3 Mercury
4 2 Impala 4 Nissan
5 2 Suburan
6 3 Milan
7 4 Altima
What I want is to do:
dealer1.his_makes # => [Ford, Chevrolet, Mercury]
dealer1.his_models # => [Flex, Fiesta, Impala, Milan]
dealer2.his_makes # => [Ford, Mercury, Nissan]
dealer2.his_models # => [Fiesta, Focus, Altima]
My question is:
Which associations/tables should I add to achieve this?.
Add an Inventory model that belongs to Dealer, CarModel, and CarMake. Toss in a 'quantity' field just for fun.
You could argue that CarModel isn't necessary, but if it's a common query, seems like a reasonable spot to de-normalize.
A join table between dealer and car makes would allow you to specify "desired car makes by dealer". If all the desired models were of the make specified in this table then I'd create a join table between DealerCarMakes and CarModels to specify the desired models for that make for that dealer.
While you could use a single table, there would be some issues:
You'd need a distinct on the query that retrieves desirable car makes, which often indicates a lack of normalisation.
Being able to specify a desirable car make would be dependent on there being a desirable car model for that make -- maybe not an issue here, but definitely an issue in other cases where there is not such a dependency.
You could not have attributes at the DealerCarMake level, such as "desirable since date" or a value range.
I'm doing an app for a membership database.
Each person may have a partner. When it comes to displaying the list, I only want to have one row for each family, so at the moment I'm comparing first names and not displaying the row if the person's name is second. Like this
person.first_name != [person.first_name, person.partner.first_name].sort[0]
This means each family only gets displayed once, not twice - once for each partner.
And I'm doing this in the view.
There must be a better way of doing this, and it'd be really great if I could do it at the database level. I'm using postgresql if that makes a difference.
Edit
Sorry if it was unclear.
Say Person 1 has the first_name "Edward" and Person 2 has the first_name "Fay". Edward and Fay are married.
I only want to show them once in my list - I want a row to look like this
Surname First name Address etc
Mysurname Edward ....
Fay
I don't want to display it again with Fay first because I've got both Fay and Edward in list of people, so I use the ruby in the first part of the question to check if I should display the row - it compares their first names and only does the row if the person has a fist name that's before his/her partner's first name.
Here's the relevant part of my person model
class Person < ActiveRecord::Base
has_one :relationship_link, :foreign_key => :person_id, :dependent => :destroy, :include => :partner
has_one :partner, :through => :relationship_link, :source => :person_b, :class_name => "Person"
I hope that's clearer
You need to use DISTINCT ON or GROUP BY. In postgres you need to be careful to group by everything that you are selecting. If you only need to get the last names you can select("DISTINCT ON(last_name) last_name").pluck("last_name"). You will only get an array of last names though.
Maybe you can get records if you order by every other fields in your table, like this:
select("DISTINCT ON(people.last_name) people.*").order("people.last_name ASC, people.first_name ASC, people.field2 DESC, people.field3 ASC...")
You need to order by every attribute so the result is not ambigious.
For this case, i would create a data structure (a Hash) to store people instances given a specific surname. Something like this:
def build_surnames_hash(people_array)
surnames_hash = {}
people_array.each do |person|
last_name = person.last_name
surnames_hash[last_name] ||= []
surnames_hash[last_name] << person
end
surnames_hash
end
That way, you can iterate over the hash and display people using their surnames stored as hash's keys:
surnames_hash = build_surnames_hash(Person.all)
surnames_hash.each do |surname, person_instances_array|
# display the surname once
# iterate over person_instances_array displaying their properties
end