Select objects from different models and union them rails - ruby-on-rails

I have to activerecord models
Model1 and Model2
Model1 has name and info fields
Model2 has state and price
I want to make an array of objects which will be a union of model1 + model2.
Each sum_model object should have name, info, state, price properties.
It should be the sum of Model1.all and Model2.all. For example:
If Model1.all returns one record with name="name" and info ="zz"
Model2.all returns one record with state='state' and price=14
model_sum should be an array of 2 objects:
[{name:'name', info: 'zz', state: '', price: ''}, {name:'', info: '', state: 'state', price: 14}]
How do I do this?

You should be able to do what you want to achieve by building the cartesian product (combine every record of one table with every table of another table) between Model1 and Model2 and then get the attributes of the resulting model (which will have the attributes of Model1 and Model2 combined:
Model1
.joins("CROSS JOIN #{Model2.table_name}")
.select("#{Model1.table_name}.*, #{Model2.table_name}.*")
.map(&:attributes)
No association needs to exist between the two models in order to do that.
Edit after rereading the question and noticing that I missed the point
You will first have to adjust the selects of both models in order to get the UNION to match columns. Luckily, one can add arbitrary selects into the AR statement, so e.g.
m1 = Model1.select(:name, :info, "'' as state", "'' as price")
m2 = Model2.select(:state, :price, "'' as name", "'' as info")
Then you will have to handcraft the UNION as this is not supported by AR. It is by Arel but I most of the time doubt that Arel is worth the struggle:
union = Model1.connection.unprepared_statement do
"((#{m1.to_sql}) UNION (#{m2.to_sql})) AS unioned"
end
And after that you can fire away the select using the union as the from value and then call the attributes method of each returned entry to achieve the result you specified:
Model1
.select('*')
.from(union)
.map(&:attributes)

Related

Rails - Passing a specific column to query by count of an associated model

I have a rails app with "recipes" model. Recipe can be liked ("like" model) by a user. What I'd like to achieve (eventually) is to sort the list of the recipes on the recipes-index page by the number of likes.
I've seen numerous threads but the two that guided me the most were:
Rails 3 ActiveRecord: order and group by association count
Rails order by results count of has_many association
I've got so far that I have an SQL-query that produces list of the recipes with the count of their likes:
SELECT recipes.name, COUNT(likes.id)
FROM recipes
LEFT JOIN likes on recipes.id = likes.recipe_id
GROUP BY recipes.name
ORDER BY COUNT(likes.id) DESC;
However, when "translating" it to something ActiveRecord can understand, I'm running into troubles.
I've concluded that using Recipe.joins(:likes) or Recipe.left_joins(:likes) passes all columns specified in "recipes", which leads to the following error:
"Column "recipes.id" must appear in the GROUP BY clause or be used in an aggregate function...".
Now, as I need to pass recipes.name and COUNT(likes.id) only, I used select:
Recipe.select("recipes.name, COUNT(likes.id)").joins(:likes).group("recipes.name").order("COUNT(likes.id) DESC")
And that's where I'm stranded as it produces this:
ActiveRecord::Relation [Recipe id: nil, name: "Scrambled eggz", Recipe id: nil, name: "Soup", Recipe id: nil, name: "Nutella pancakes"]
Recipe.joins("LEFT JOIN likes ON recipes.id = likes. recipe_id").group("recipes.id").order("COUNT(likes.id) ASC")
ActiveRecord parsing expects the returned resultset to have the columns that the original model has(Or a subset of columns).
When selecting a subset of the original columns, ActiveRecord nullifies all the other columns while parsing it in an ActiveRecord object/relation
This may help you if you want to it in active record query:
Recipe.left_joins(:likes).group(:id).order("COUNT(likes.id) DESC")
Warning for the JOIN:
Recipe.joins(:likes).group(:id).order("COUNT(likes.id) DESC")
This query will only return the recipes which have likes. If there are any recipes that don't have any likes, doing JOINS won't get you those.
Your query is correct. Please note that it use Recipe model class, which does not contains your custom field in its Class. If your query is
Recipe.select("COUNT(likes.id)").joins(:likes).group("recipes.name").order("COUNT(likes.id) DESC")
It will show you
ActiveRecord::Relation [Recipe id: nil, Recipe id: nil, Recipe id: nil]
However, You still can access your custom fields, the returned data hold it for you to use. You just need to give it a name to call it. Also, You can use that custom field to order, please don't direct order COUNT
recipes = Recipe.select("COUNT(likes.id) as total_likes").joins(:likes).group("recipes.name").order("total_likes DESC")
recipe.first.total_likes # TOTAL LIKE of first recipe

Sort ActiveRecord relation by JSON attribute from joined table

How can I sort an ActiveRecord relation in ascending order, based on the value of an attribute, where that attribute exists in a joined table, and is JSON?
First, I'm querying for members and joining the skills table.
members = Member
.select('
member.id,
member.skills_id,
member.name,
member.position,
skills.data as data')
.join('left join skills on skills.id = member.skills_id')
In each member record, skills.data is a JSON with 2 keys, technical and general,
ie: {'technical': 'accounting', 'general': 'planning'}
I would like to do something like,
if project.status == 'complex'
members.sort_by {|x| x['data']['technical']}
else
members.sort_by {|x| x['data']['general']}
end
So I can order members based on their skill alphabetically, based on different cases. Is this possible?
Apologies if I did not explain this clearly.
Pure SQL solution : if you want n result based on the order
order by (skills.data->'technical')::text
Sort_by solution :
members.sort_by! { |member| member.data['technical'].to_s }

Mongo Mapper rails querying join (or attr_accessor) father and children object

I have two models for example:
father
key Name, String
many :childrens
children
key type, String
belongs_to :father
I want to query all the fathers that has a children with a particular type for example type == "home"
Which is the more efficient way to do such a task (I'm using mongo mapper)?
I would like to use a query and not a select function on the father collection.

testing queries using rspec and factory_girl : Is there a better way?

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

finding records where belongs_to table's column is not equal

I have a Trainer model that has a has_many relationship with Pokemon. How do I find all Trainers who do not have any Pokemon of a certain type or group of types(type is a column in Pokemon model)?
The code I have tried, but it returns a trainer if any of his Pokemon's types is not in the group (e.g. if a trainer has pokemon of type fire and electric, he will be returned because electric isn't in the array. I don't want him returned because he has a fire pokemon.)
Trainer.joins(:pokemons).where("pokemons.type NOT IN (?)", ["fire","grass","water"])
The same problem occurs when I'm just comparing to one type.
Trainer.joins(:pokemons).where("pokemons.type != ?", "fire")
Again, the example trainer will get returned because he has a pokemon with a type, electric, that is not equal to fire.
I am using Rails 3.2.13 and Ruby 1.9.3.
I don't think there is a way to write this with Rails in one query, but you can do the following:
Trainer.where('trainers.id NOT IN (?)', Pokemon.where(type: ['grass', 'fire', 'water']).pluck(:trainer_id).uniq )
Explained version:
# selects the IDs of Trainer having a Pokemon of type Grass||Fire||Water
trainer_ids = Pokemon.where(type: ['grass', 'fire', 'water']).pluck(:trainer_id)
# returns the trainers
Trainer.where('trainers.id NOT IN (?)', trainer_ids)
In Rails 4, you can do:
Trainer.where.not(id: Pokemons.select(:trainer_id).where("pokemons.type IN (?)", ["fire","grass","water"])
This will grab all the trainers where their id does not show up in the list of trainer_ids on pokemons with fire, grass, or water.
An alternative is doing the query on the SQL layer through ActiveRecord's find_by_sql:
Trainer.find_by_sql [
"SELECT * FROM trainers
WHERE id NOT IN
(SELECT DISTINCT(trainers.id) FROM
trainers JOIN pokemons
ON trainers.id = pokemons.trainer_id
WHERE pokemons.type IN ('grass', 'fire','water'))"
]

Resources