concatenating objects in ruby - ruby-on-rails

I have an object called teacher and another one called students. I am trying to return both of them in a controller as json response, and I want the response to be like
{'teacher': {'first_name': 'adam', 'last_name': 'smith'}, 'students': [{'id':'5', 'age' :15}, {'id':'8', 'age' :18}]}
or in case of one to one relationship(one teacher has one student):
{'teacher': {'first_name': 'adam', 'last_name': 'smith'}, 'students':{'id':'8', 'age' :18}}
The point is to return a concatenated json response where one object appears inside the other one
I have tried teacher.as_json.merge(students.as_json), but this does not embed the students objects inside the teacher. it just append the data
I have read in one post the teacher['students'] = students should work but I get always an error: can't write unknown attribute students

teacher.as_json.merge(students: (students.count > 1 ? students.as_json : students.first.as_json ))

Do exactly as you would in JSON. If you have teacher object and students object, render this object:
{ teacher: teacher, students: students }

Related

Rails distinct values of a value returned by a method in model

Suppose I have an Employee model, there is a method in Employee model named def fixed
def fixed
return self.cached_fixed.to_f if self.cached_fixed.present?
return (self.current_salary && self.current_salary.fixed).to_f
end
end
def current_salary
return #current_salary if #current_salary
# #current_salary = self.employee_salaries.detect{|es| es.end_date == nil}
#current_salary = self.db_current_salary
return #current_salary
end
if the fixed were a column in employee table we could have just used Employee.distinct.select(:fixed) to pull the distinct values
is there a way if it's just a method not a field in table without loading all the employees.
I am expecting to get the unique values of a column from a table , but it may not be a column as in the above table
Not for an arbitrary method, no. But you start the query from the EmployeeSalary end and fetch only the column you care about in one query using select:
EmployeeSalary
.select(:fixed)
.join(:employee)
.where(end_date: nil)
This will run a select fixed from... query and return a list of EmployeeSalary objects, but all the fields that aren't listed in the select call will be nil. Assuming the constraint of only one salary record having end_date: nil, there will be one EmployeeSalary object per employee. You can add .distinct in the method chain if you want unique values.
I'm not sure how the caching logic fits into this question. You can apply caching logic on top of that list if you like, but doing one query like this is pretty fast.

Filter relations similar to any? in Rails

I have a Person model which has_many pets. There is a method in Pet model called is_parent?
How do I filter pets of a person based on whether the pets already have child or not?
I want something similar like
person = Person.first
person.pets.any? { |pet| pet.is_parent? } # returns true
Is there something I can call like person.pets.filter { |pet| pet.is_parent? } and returns all the pets with children?
Thank you
You can use the select method from Ruby's Enumerable module like this
person.pets.select { |pet| pet.is_parent? }
Which can also be written as
person.pets.select(&:is_parent?)
That being said, with lots of records it is usually best to do the filtering on the database side.

Ruby On Rails 5, activerecord query where model association ids includes ALL ids in array

I have a Has many belongs to association for Recipes and Ingredients.
I am trying to return recipes which have all the ingredient ids in a given array of integers.
Eg. I search using multiple ingredients, and I want to return any Recipe that has all the ingredients. If too many ingredients are given, but the Recipe includes them all, it should also return the recipe.
I have tried a few things,
Recipe.joins(:ingredients).where(ingredients: { id: ids })
which returns the Recipes that have ANY of the ingredients
Recipe.joins(:ingredients).where('ingredients.id LIKE ALL ( array[?])', ids)
This gives an error: ERROR: operator does not exist: integer ~~ integer
How can I find only the recipes that include all of the ids given in the array?
Try this query:
# Ingredient ID array
ingredient_ids = [....]
Recipe.joins(:ingredients).where(ingredients: { id: ingredient_ids }).
group("recipes.id").
having('count(recipes.id) >= ?', ingredient_ids.size)
First line ensure that only recipes having any of the ingredients are fetched
Second & third lines ensure that loaded recipes have minimum ingredient size of the id array, so that if any of the ingredient is missing it won't be considered.
Hope it helps!

How to implement partial update via rails api

Let's say we have a resourceful Student model. I have a query regarding updating a student resource via PUT api.
If we send a put request to PUT /students/1 along with request body containing the few attributes that we want to update.
Let's the Student having many attributes like name,age,rollno,country,dob and we just want to update the country, so in the put request body we will pass something like {country: 'germany'} , some other request might pass only the dob.
How should we handle it in server side to only update the attributes passed in the body ?
The update method on your ActiveRecord objects takes an attributes hash. You can pass only one attribute, or all attributes of a model, and ActiveRecord will figure out what has changed and only update those columns and leave the rest alone.
student = Student.create(name: 'name', age: 'age', rollno: 'rollno', country: 'country', dob: 'dob')
params = { country: 'germany' } # stand-in for your actual params hash in the controller
student.update(params)
Only country will be updated, everything else will remain the same. On the next request when you update dob it works the same way.
params = { dob: '1/1/2000` }
student.update(params)

RubyOnRails multiple scopes composition

I got a Product model with has_many Types table and several scopes:
class Product < ActiveRecord::Base
has_many :product_types
has_many :types, through: :product_types
scope :type1, -> { joins(:types).where(types: { name: "Type1" }) }
scope :type2, -> { joins(:types).where(types: { name: "Type2" }) }
end
When I try to use one scope (Product.type1 for example) all goes well, but two scopes at a time (Product.type1.type2) returns an empty query. Yes, one product may have multiple types.
Final goal is to create a filter of products by type with checkboxes form. When I check type1 and type2 I want to see all my products that have Type1 and Type1 at the same time.
UPD 1
So I've tried to do several queries and then & them as #aaron.v suggested. I wanted to do the logic inside of the function so:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
My point was to iterate through each type, collect all products and then & them inside the function.
The problem is when I'm iterating, each loop returns string instead of array of hashes.
Product.joins(:types).where(types: { name: types }).distinct # returns array of hashes, it's okay.
types.each { |type| Product.joins(:types).where(types: { name: type }).distinct } # each loop returns string (Type name) instead of array of hashes.
What am I doing wrong?
SOLUTION 1
Suggested by #aaron.v, explained below
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
SOLUTION 2
Found on reddit.
In this function you are fetching all products with at least one required type and than grouping only ones that have required count of types. Thus, you get only those products that belongs to every type at the same time.
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct.group('products.id').having('count(*) = ?', types.each.count)
end
If you have a database background, this would be pretty obvious as to why you wouldn't be able to find products with multiple types based off how you are writing your scopes.
Database queries that are written from these scopes will multiply the rows to ensure that a product that has many types, will have a distinct row for each type. Your query when you combine both scopes is writing
where `types`.name = "Type1" AND `types`.name = "Type2"
This will never happen since columns aren't added with multiples of the same row on a join.
What you want to do is have a method that you can pass an array of type names to find it
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct
end
This method can accept one name by itself or an array of names for the types you want to find
If you pass an array of type names like ["type1", "type2"], this will result in a query like
where `types`.name in ("type1", "type2")
And then you should see the results you expect
UPDATE
To revise what you need in finding products that have all types given to your method, I decided to do this
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
This method requires an array of type names(even if it is one type, pass an array with one type in it). What it will do is find all products with one specific type name, only select the product id(which will be lighter on your DB) and map it into an array which will get dumped into the product_ids array. after it has looped over each type name, it will find all products that intersect in each of the arrays resulting with products that have all types passed in.

Resources