get parent object filter by nested child fields - ruby-on-rails

Inside teacher, there is child subject, and subject has a child student. I want to get all teachers filter by student_name. Teacher object can be something like this:
{ "id": 5,
"name": "teacher-one",
"gender": "male",
"subject": {
"id": 10,
"subject_name": "Maths",
"student": {
"id": 1,
"student_name": "student-one",
"grade" : "one",
}
}
}
I am looking to filter this out by using find method, something like
Teacher.find(:all, params: {gender: "male'}) which returns all the male teachers. But the following code does not work:
Teacher.find(:all, params: { subject: { student: grade == "one" }})

Try this:
Teacher.joins(subject: :student).where(student: {student_name: "Bala"})

Related

OData GroupBy and Select

Expecting the following example table CustomerOrders
Id
CustomerId
Customer
Product
1
1
Alice
Pizza
2
1
Alice
Pasta
3
2
Bob
Burger
In C# I'm was able to use the following Linq query to produce a nice List<Customer> result with a nested orders collection for every customer:
List<CustomerOrders> queryResult = GetCustomerOrders();
return queryResult
.GroupBy(x => x.CustomerId)
.Select(x => new Customer
{
Id = x.First().CustomrId,
Customer = x.First().Customer,
Orders = x.ToList()
})
.ToList();
Now I want to achive this result directly over an odata query in the client application to get the following JSON result:
[
{
"id": 1,
"customer": Alice,
"orders": [ "Pizza", "Pasta" ]
},
{
"id": 2,
"customer": Bob,
"orders": [ "Burger" ]
}
]
Is there a way to transfer this query in odata?
GroupBy in OData is similar to SQL, only the aggregates and common columns are returned, we lose access to the individual items, so we can return a grouping and a count of the orders, using group by, but not the array of orders.
If your schema has a Customer entity and there is a collection navigation property from Customer to Orders, then we do not need to use grouping at all:
~/Customers?$expand=Orders($select=Product)&$select=Id,Name
The output is structured in a slightly similar manner and should resemble something like this:
{
"#odata.context": "~/$metadata#Customers(Id,Name,Orders(Product))",
"value": [
{
"Id": 1,
"Name": "Alice",
"Orders": [{"Product": "Pizza"},
{"Product": "Pasta"}]
},
{
"Id": 2,
"Name": "Bob",
"Orders": [{"Product": "Burger"}]
}
]
}
A key concept in OData is that the shape of the overall graph should not be modified, it is designed deliberately to always maintain the structure of the Entities that are returned. This means that the definition document is always correct, the only thing missing from this response is the additional fields that were not requested.
If you need the output in the client specifically as mentioned, then you can expose that as a custom function on the controller:
[EnableQuery]
public IQueryable<CustomerSummary> GetCustomersWithOrderSummary()
{
List<CustomerOrders> queryResult = GetCustomerOrders();
return queryResult
.GroupBy(x => x.CustomerId)
.Select(x => new CustomerSummary
{
Id = x.Key,
Customer = x.First().Customer,
Orders = x.Select(o => o.Product)
});
}
If using GroupBy, the closest response we can get is this:
~/CustomerOrders?$apply=groupby((CustomerId,Customer),aggregate($count as Orders))
But here we will return a count of the orders, and not an array of the product values as expected:
{
"#odata.context": "~/$metadata#CustomerOrders(CustomerId,Customer,Orders)",
"value": [
{
"#odata.id": null,
"CustomerId": 1,
"Customer": "Alice",
"Orders": 2
},
{
"#odata.id": null,
"CustomerId": 2,
"Customer": "Bob",
"Orders": 1
}
]
}

Decoding Multiple JSON URLs into a Single Class

I'm attempting to decode multiple JSON URLs into a class. Since I'm using an API, I have no control over the fact that the JSON data is split into two URLs. Here is the problem statement:
I have a class called Student
JSON URL student_info.json contains a dictionary with all students and the Student instance constants A, and B.
JSON URL student_status.json contains Student instance variables C, D, and E.
I need to do the following:
Retrieve the dictionary of students from student_info.json to populate a collection of Students and instantiate them with attributes A and B.
Save this Student collection so that it is accessible throughout the rest of the app's lifecycle.
Retrieve the dictionary of student information from student_status.json and populate Student instance variables C, D, and E for each student in the collection.
From time to time, update the collection of students (in case any were dropped or added).
From time to time, update variables C, D, and E for each student in the collection.
Here are some example files:
File:
student_info.json
Content:
{
"last_updated": 1535936693,
"tyl": 10,
"data": {
"students": [
{
"student_id": "1",
"name": "John Appleseed",
},
{
"student_id": "2",
"name": "Jane Doe"
}
]
}
}
File:
student_status.json
Content:
{
"last_updated": 1535936693,
"tyl": 10,
"data": {
"students": [
{
"student_id": "1",
"number_of_classes": "10",
"GPA": "2.87",
"tuition_due": "237.33"
},
{
"student_id": "2",
"number_of_classes": "10",
"GPA": "2.87",
"tuition_due": "237.33"
}
]
}
}
File
Student.swift
Content
class Student {
var student_id: Int
var name: String
var numberOfClasses: Int?
var gpa: Double?
var tuitionDue: Double?
}

Rails 4 - Iterate through nested JSON params

I'm passing nested JSON into rails like so:
{
"product": {
"vendor": "Acme",
"categories":
{
"id": "3",
"method": "remove",
},
"categories":
{
"id": "4"
}
}
}
in order to update the category on a product. I am trying to iterate through the categories attribute in my products_controller so that I can add/remove the product to multiple categories at once:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: params[:product][:categories][:id])
if params[:product][:categories][:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end
However, this only uses the second 'categories' ID in the update and doesn't iterate through both.
Example response JSON:
{
"product": {
"id": 20,
"title": "Heavy Duty Aluminum Chair",
"product_price": "47.47",
"vendor": "Acme",
"categories": [
{
"id": 4,
"title": "Category 4"
}
]
}
}
As you can see, it only added the category with ID = 4, and skipped over Category 3.
I'm fairly new to rails so I know I'm probably missing something obvious here. I've played around with the format of the JSON I'm passing in as well but it only made things worse.
You need to change your JSON structure. As you currently have it, the second "categories" reference will override the first one since you can only have 1 instance of a key. To get what you want, you should change it to:
{
"product": {
"vendor": "Acme",
"categories": [
{
"id": "3",
"method": "remove",
},
{
"id": "4"
}
]
}
}
You will also need to change your ruby code to look like:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: u[:id])
if u[:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end

How to have two pieces of data output as json in rabl

New to rabl and not sure how to do this with two different arrays returned in a single hash like this:
#data={:locations => [location1, location2], :items => [item1,item2]}
In my rabl file, I'd like to do something like the following:
#data[:locations]
extends "api/location_show"
#data[:items]
extends "api/item_show"
to output this:
{
"locations": [
{
"id": 156,
"name": "Location 1"
},
{
"id": 158,
"name": "Location 2"
}
],
"items": [
{
"global_id": 3189,
"header": "pistachio 1"
},
{
"global_id": 3189,
"header": "pistachio 2"
}
]
}
but it just doesn't seem to be working. Is there a way to get this to work?
thx
Your rabl file should look something like:
object false
child (:locations) { attributes :id, :name }
child (:items) { attributes :global_id, :header }
By setting object to false, you essentially tell rabl that you want to construct your nodes on your own. Then you can go ahead and invoke the child and node methods as you wish.

Output JSON array without the class name in every array element

The default way to output JSON in rails is some thing like:
Code:
render :json => friends.to_json(:only => [:username, :avatar_file_name, :id ])
Output
{"friends" :
[{"user":
{"avatar_file_name": "image1.jpg", "username": "user1", "id": 1}},
{"user":
{"avatar_file_name": "image2.jpg", "username": "user2", "id": 2}},
{"user":
{"avatar_file_name": "image3.jpg", "username": "user3", "id": 3}}
]}
But i want something like:
{"friends" :
{"user": [
{"avatar_file_name": "image1.jpg", "username": "user1", "id": 1},
{"avatar_file_name": "image2.jpg", "username": "user2", "id": 2},
{"avatar_file_name": "image3.jpg", "username": "user3", "id": 3}
]}
}
The class is specified by the array name.
Last.fm also uses this syntax see Last.fm 'API-user.getfriends'
The solution to this problem is commenting the line
ActiveRecord::Base.include_root_in_json = true
in initializers/new_rails_defaults.rb
Or setting ActiveRecord::Base.include_root_in_json to false.
You can use javascript to reformat it:
var json =
{
"friends" :
{ "user": [] }
}
var i = 0;
for ( x in friends )
{
json.friends.user[i].avatar_file_name = x.user.avatar_file_name; // add more fields.
i++;
}
Something among those lines.
JSON is normally used to represent objects in a text format.
So if you like the secon output you must change your objects.
The first output says:
there is a friends object which is a array of user, each user has some properties among which you chose to expose username, avatar_file_name, id
The second output says:
there is a friends object which contains a user object which is an array of unnamed objects, each unnamed objects has some properties...
This second output is not writable in JSON syntax.
It might be:
{"friends" :
{"user": [
["avatar_file_name", "username", "id"],
["image1.jpg", "user1", 1],
["image2.jpg", "user2", 2],
["image3.jpg", "user3", 3]
]}
}
This says:
there is a friends object which contains a user object which is an array of array (a table with field names on first row) ...

Resources