rails: query has_many for a list of objects - mysql

I have a model City:
class City
belongs_to :country
end
And a model Street:
//street has an attribute `Type` which can be 1, 2 or 3
class Street
belongs_to City
end
I want all cities in Croatia including streets that are of type 2
So something like this:
cities = City.find_by_country("Croatie").include_streets_where(type: 2)
so I get something like this:
[
{
name: "Zagreb",
country: "Croatia",
streets: [{name: "street1", type: 2},{name: "street2", type: 2}]
},
{
name: "Split",
country: "Croatia",
streets: [{name: "street3", type: 2},{name: "street4", type: 2}]
}
]
My solution is to first get the cities by country name and loop through each city to query it's streets. But I'm guessing there is a better way.

I'm assuming your City has_many :streets, and your Country class has an attribute name.
A 2-layered loop is not as efficient as an INNER JOIN, which you can assemble with this: (You can look at the SQL it generates by appending .to_sql to the end of it.)
cities = City.where(country: Country.find_by_name("Croatie"))
.joins(:streets).where(streets: { type: 2 })
This will return a list of city objects matching your criteria. Now to get it to the format you specified, you have to do some formatting on the Ruby side since the default returned is not an Array type. This is assuming you want an array of hashes.
formatted_list = cities.map do |city|
{ name: city.name,
country: city.country.name,
streets: list_of_streets_with_type(city.streets) }
end
def list_of_streets_with_type(streets)
streets.map do |street|
{ name: street.name,
type: street.type }
end
end
In the end, formatted_list would be returning what you wanted.
(Disclaimer: I have not checked syntax, but the general idea is there. Give it a try, it should work)

Related

Finding a record with has many relationship and other params Ruby on Rails MySQL

I have two tables, pets and owners
class Owner < ApiModel
has_and_belongs_to_many :pets
and
class Pets < ApiModel
has_and_belongs_to_many :owners
So the example is three Owners, Frank, Mary, Hillary, who live together and own three pets (doggy, kitty, and fishy)
Owner1 = {name: "Mary", gender: "female", hair_color: "blue", pets: [pet1, pet2, pet3]}
Owner2 = {name: "Hilary", gender: "female", hair_color: "green", pets: [pet1, pet2]}
Owner3 = {name: "Frank", gender: "male", hair_color: "red", pets: [pet3]}
pet1 = {name: "doggy", gender: "female", animal: "dog"}
pet2 = {name: "kitty", gender: "male", animal: "cat"}
pet3 = {name: "fishy", gender: "male", animal: "fish"}
My goal is to return Owner 1 given that I know she owns pet3 and that she is female.
I thought I could do something like this:
found_pet = Pet.find_by(animal: "fish") # will correctly only return the pet3 record
owner = Owner.where(hair_color: "blue").includes(pet: found_pet)
But I keep getting an error (Object doesn't support #inspect) when trying to find owner.
Is this maybe possible with using .join ?
Rails version 6.0.4.7
Ruby version ruby 3.1.1p18
UPDATE (Answered in Comments)
Christos-Angelos Vasilopoulos had a good response for my original question but I had a follow up.
So what if I want to find where the owner own two pets:
found_pet1 = Pet.find_by(animal: "dog")
found_pet2 = Pet.find_by(animal: "cat")
owner = Owner.where(hair_color: "blue").includes(pet: found_pet1).includes(pet: found_pet2)
The best approach would be to use the association. Executing pet_record.owners returns all the pet owners. Then you need a query to return the right results.
Use find_by to get the first matching record for your query
found_pet.owners.find_by(hair_color: "blue")
Use where to get all the matching records for your query
found_pet.owners.where(hair_color: "blue")
PS: In your question your state that you need to return Owner1 as it is female, then I would suggest doing the following.
found_pet.owners.find_by(gender: "female")
found_pet.owners.where(gender: "female")

How SQL get nested relation item with limit pagination

I need get a products which under Category but with a limit of products item for paginate purpose. I am use strapi headlessCMS for my backend. i will query it from "slug" in category. And then it will return collection as below code, but i need a LIMIT for my products list.
Since Strapi is use bookshelf.js in sql. i have tried it as exmaple 2 below. I am bad in handle data, hope someone sharing idea how to do or give some solution for me. Appreciate =).
If you know in raw sql statement, please sharing with me. I'll do the search for it. The important part is given a logically idea how to do with data. So, my brain has the map can do the searching. Thanks.
Model relationship:
Category hasMany Products
Example 1
const res = await strapi.query("category").findOne({ slug });
// return
{
id: 2,
slug: 'slug-string'
....
products: [
{
id: 1,
name: 'Ice Cream',
category: 2
},
{
id: 2,
name: 'Bread',
category: 2
},
....
{
id: 100,
name: 'Paper',
category: 2
}
]
}
Example 2: i guess this is bad practice ya? Because it query all products, the better way i think is example 1.
const Product = strapi.query("product").model;
const result = await Product.query((qb) => {
qb.where("category", 2);
}).fetchPage({
pageSize: 1,
page: start, // start is a number, it will pass from front end query page=2
});

JSON multiple alias names angular 8

I have below interface.
interface ProductJson {
id: number;
name: string;
price: number;
}
I want to have multiple alias names for price, like price and alias names: rate, etc. How can I read json data attribute 'rate' for 'price' and also read 'price' too.
You can use a custom serializer to create aliases between fields:
Example using #kaiu/serializer:
class ProductJson {
id: number;
name: string;
#FieldName('rate')
price: number;
}
Or you can also create a getter method and use the serializert to map your JSON to a class instance in order to use your method directly, see https://kaiu-lab.github.io/serializer/ for in-depth stuff.
One way is to maintain a group of attribute names that you want to alias.
And then add the interface property price to the json itself, if it contains the aliased properties like rate or amount.
Now you can simply access price from else where, which should give the same value
Ex:
var price_group = ['price', 'rate', 'amount'];
var some_other_group = []
var resp = {rate: 200, p: 12}
var resp2 = {price: 300, p: 12};
Object.keys(resp).forEach(key => {
if(price_group.indexOf(key) > -1){
resp.price = resp[key]
}
});
console.log(resp.price)
Object.keys(resp2).forEach(key => {
if(price_group.indexOf(key) > -1){
resp.price = resp[key]
}
});
console.log(resp2.price)
I'm not sure you can do that tbh.
You can easily do it by programming your stuff that reads/writes the json to accept stuff like rate, price, moolah and just write it as
{
price: number
}
edit: what i'm saying is you take the user input or any other input that specifies something like {moolah: 30} and you take that '30' and put it on {price: 30} in your json.

Accessing an included object

I have the following JSON Object, which is the result of a loopback model (Classifications), with a relationship with another model (Labels).
My call to get the classifications is:
modClassification.findOne({
where: { id: classificationid },
include: 'labels' },
function( err, classification ){ ...
And this returns classification with something like
{ id: 'b01',
title: 'population',
country_id: 1,
labels:
[ { column_id: 'b1',
classification_id: 'b01',
classification_title: 'population',
dsporder: 1,
label: 'Total_Persons_Males',
country_id: 1,
id: 1 },
{ column_id: 'b2',
classification_id: 'b01',
classification_title: 'population',
dsporder: 2,
label: 'Total_Persons_Females',
country_id: 1,
id: 2 } ] }
which is what I would expect.
I now need to loop over the labels and access it's properties, but this is where I am stuck.
classification.labels[0] = undefined..
I have tried looping, each and whatever I can find online, but can't seem to get to each labels properties.
Can someone tell me what I am doing wrong/need to do?
Thanks
When you are including related models inside a findOne call, you need to JSONify the result before accessing the related records:
classification = classification.toJSON()
Then you should be able to access the included label items as you expect.
See https://docs.strongloop.com/display/public/LB/Include+filter, specifically the "Access included objects" section.
Note this does not work the same when you retrieve more than one result in an array. In that case you'll need to perform toJSON() on each item in the array.

Prevent junction-table data from being added to json with sequelize

I've got the following models associated with sequelize.
Event hasMany Characters through characters_attending_boss
Boss hasMany Characters through characters_attending_boss
Characters hasMany Event through characters_attending_boss
Characters hasMany Boss through characters_attending_boss
These tables are successfully joined and I can retrieve data from them. But when I retrieve the JSON-results the name of the through model gets added to each object, like this:
{
id: 1
title: "title"
-confirmed_for: [ //Alias for Event -> Character
-{
id: 2
character_name: "name"
-confirmed_for_boss: [ // Alias for Boss -> Character
-{
id: 9
name: "name"
-character_attending_event: { // name of through-model
event_id: 1
char_id: 2
}
}
]
-character_attending_boss: { // name of through-model
event_id: 1
char_id: 2
}
}
So I'm looking for a way to hide these "character_attending_boss" segments if possible, preferably without altering the results post-fetch.
Is this possible?
Same issue is solved here: https://github.com/sequelize/sequelize/issues/2541
For me adding through: {attributes: []} to include block works well on Sequelize 2.0
Pass { joinTableAttributes: [] } to the query.