Rails mongoid has_one queries - ruby-on-rails

In User model there is has_one relation to Professional. In the professional model I have one Array field named industries.
I need to take all values where professional industries in "IT"
I tried User.where(:"professional.industries".in => ["IT"])
But Its not working. Any sugestions..??

In order for your query to work you should use
class User
embeds_one :professional
end
If you are sure that Professional should be a separate collection you could use something like:
uids = Professional.where(:"industries".in => ["IT"]).distinct(:user_id)
users = User.where(:_id.in => uids)

Related

Rails Activerecord query through three associations?

Company has_many :agents, Agent belongs_to :company.
Agent has_many :comments, Comment belongs_to both :agent and :business.
Business has_many :comments.
What ActiveRecord query finds all the businesses with a comment written by an agent of the company?
I can get all the comments for all the agents of a company:
#company.agents.joins(:comments)
And using that query I can pluck all the business_ids that are commented on by company agents:
#company.agents.joins(:comments).pluck(:business_id)
(So if nothing else works I could get the desired list of businesses using a second query into Business given an array of IDs.)
However I cannot seem to extend the association chain in a single query to includes the Businesses, eg, a query that finds the Business record for each business commented upon by company agents, eg something like:
#company.agents.joins(:comments).joins(:business) # Can't join 'Agent' to association named 'businesses'
EDIT:
Tried #company.agents.joins(:comments => :business) as suggested by Jon in the comments. That works if doing a .count() or a .pluck().
If need to also query by Business fields, that can be done with:
#company.agents.joins(:comments => :business).where(:businesses => {:account_status => :active})
You're adding the second join query onto the agents scope. You'll need to use something like the following:
#company.agents.joins(:comments => :business)
If you need to add conditions against the business you can do so as follows:
#company.agents.joins(:comments => :business).where(:businesses => {:account_status => :active})
This way it correctly chains the join queries for you and adds the conditions to the appropriate tables.

Search for model by multiple join record ids associated to model by has_many in rails

I have a product model setup like the following:
class Product < ActiveRecord::Base
has_many :product_atts, :dependent => :destroy
has_many :atts, :through => :product_atts
has_many :variants, :class_name => "Product", :foreign_key => "parent_id", :dependent => :destroy
end
And I want to search for products that have associations with multiple attributes.
I thought maybe this would work:
Product.joins(:product_atts).where(parent_id: params[:product_id]).where(product_atts: {att_id: [5,7]})
But this does not seem to do what I am looking for. This does where ID or ID.
So I tried the following:
Product.joins(:product_atts).where(parent_id: 3).where(product_atts: {att_id: 5}).where(product_atts: {att_id: 7})
But this doesn't work either, it returns 0 results.
So my question is how do I look for a model by passing in attributes of multiple join models of the same model type?
SOLUTION:
att_ids = params[:att_ids] #This is an array of attribute ids
product = Product.find(params[:product_id]) #This is the parent product
scope = att_ids.reduce(product.variants) do |relation, att_id|
relation.where('EXISTS (SELECT 1 FROM product_atts WHERE product_id=products.id AND att_id=?)', att_id)
end
product_variant = scope.first
This is a seemingly-simple request made actually pretty tricky by how SQL works. Joins are always just joining rows together, and your WHERE clauses are only going to be looking at one row at a time (hence why your expectations are not working like you expect -- it's not possible for one row to have two values for the same column.
There are a bunch of ways to solve this when dealing with raw SQL, but in Rails, I've found the simplest (not most efficient) way is to embed subqueries using the EXISTS keyword. Wrapping that up in a solution which handles arbitrary number of desired att_ids, you get:
scope = att_ids_to_find.reduce(Product) do |relation, att_id|
relation.where('EXISTS (SELECT 1 FROM product_atts WHERE parent_id=products.id AND att_id=?)', att_id)
end
products = scope.all
If you're not familiar with reduce, what's going on is it's taking Product, then adding one additional where clause for each att_id. The end result is something like Product.where(...).where(...).where(...), but you don't need to worry about that too much. This solution also works well when mixed with scopes and other joins.

Mongoid: How do I query for all object where the number of has_many object are > 0

I have a Gift model:
class Gift
include Mongoid::Document
include Mongoid::Timestamps
has_many :gift_units, :inverse_of => :gift
end
And I have a GiftUnit model:
class GiftUnit
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :gift, :inverse_of => :gift_units
end
Some of my gifts have gift_units, but others have not. How do I query for all the gifts where gift.gift_units.size > 0?
Fyi: Gift.where(:gift_units.exists => true) does not return anything.
That has_many is an assertion about the structure of GiftUnit, not the structure of Gift. When you say something like this:
class A
has_many :bs
end
you are saying that instance of B have an a_id field whose values are ids for A instances, i.e. for any b which is an instance of B, you can say A.find(b.a_id) and get an instance of A back.
MongoDB doesn't support JOINs so anything in a Gift.where has to be a Gift field. But your Gifts have no gift_units field so Gift.where(:gift_units.exists => true) will never give you anything.
You could probably use aggregation through GiftUnit to find what you're looking for but a counter cache on your belongs_to relation should work better. If you had this:
belongs_to :gift, :inverse_of => :gift_units, :counter_cache => true
then you would get a gift_units_count field in your Gifts and you could:
Gift.where(:gift_units_count.gt => 0)
to find what you're looking for. You might have to add the gift_units_count field to Gift yourself, I'm finding conflicting information about this but I'm told (by a reliable source) in the comments that Mongoid4 creates the field itself.
If you're adding the counter cache to existing documents then you'll have to use update_counters to initialize them before you can query on them.
I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
So, get this done, you can define a class function like so:
def self.with_units
ids = Gift.all.select{|g| g.gift_units.count > 0}.map(&:id)
Gift.where(:id.in => ids)
end
The advantage is, that you can do all kinds of queries on the associated (GiftUnits) model and return those Gift instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:
Gift.with_units.where(:some_field => some_value)

ActiveRecord select through multiple joins

I'm working on a Rails-based web service that provides data about various sports team schedules. I have models that included the following:
Each Game record has a "home" and an "away" team, each of which references a Team record
Each Team record belongs to a Division record
I have modeled Game as follows:
class Game < ActiveRecord::Base
# Miscellaneous validations here
belongs_to :home_team, :class_name => Team
belongs_to :away_team, :class_name => Team
# Other stuff follows
end
Here is the model for Team:
class Team < ActiveRecord::Base
# Miscellaneous validations here
belongs_to :division
# Other stuff follows
end
And here is the model for Division:
class Division < ActiveRecord::Base
# Miscellaneous validations here
has_many :teams, :dependent => :destroy
# Other stuff follows
end
I am trying to implement a request to return all games where the home team and the away team are both from a specific division. In pseudo-code , I want something like:
SELECT games.* FROM games WHERE
"The division ID of the home team" = '1' AND
"The division ID of the away team" = '1'
I've tried various incarnation using the joins method, none of which have worked for me. The closest I've come is this:
games = Game.joins(:home_team, :away_team).where(
:home_team => {:division_id => params[:division_id]},
:away_team => {:division_id => params[:division_id]})
but this gives me an error:
SQLite3::SQLException: no such column: home_team.division_id: SELECT "games".* FROM "games" INNER JOIN "teams" ON "teams"."id" = "games"."home_team_id" INNER JOIN "teams" "away_teams_games" ON "away_teams_games"."id" = "games"."away_team_id" WHERE "home_team"."division_id" = '1' AND "away_team"."division_id" = '1'
Clearly, my syntax for specific home_team and away_team isn't working because it's not mapping them to a valid table name of "teams". But any other variant of the join I come up with seems to get me even further from what I want.
I'd appreciate any help you can provide or references to documentation that shows me how to do this kind of thing.
While the answer provided by #tsherif did the trick for me, I wanted to also share an alternate approach I figured out based on info I found elsewhere.
It turns out that ActiveRecord implements its own rules for table aliasing when you reference the same table twice in a joins. This aliasing is described in the Table Aliasing section of this link. Based on the information, I was able to determine that the second association listed in my join (:away_team) was being aliased as away_teams_games. With this table alias in mind I was able to get things working using this:
games = Game.joins(:home_team, :away_team).where(
:teams => {:division_id => params[:division_id]},
:away_teams_games => {:division_id => params[:division_id]})
While this wasn't completely obvious to me when I first looked at it, now that I see what is happening it makes some sense.
I think you might be able to try something like this:
Game.where(["(SELECT COUNT(DISTINCT teams.id) FROM teams WHERE teams.division_id = ? AND (teams.id=games.home_team_id OR teams.id=games.away_team_id)) = 2", params[:division_id]])
You have the nested query, which is a little annoying, but it lets you avoid having to join twice against the teams table.

Ruby on Rails Associations

I am starting to create my sites in Ruby on Rails these days instead of PHP.
I have picked up the language easily but still not 100% confident with associations :)
I have this situation:
User Model
has_and_belongs_to_many :roles
Roles Model
has_and_belongs_to_many :users
Journal Model
has_and_belongs_to_many :roles
So I have a roles_users table and a journals_roles table
I can access the user roles like so:
user = User.find(1)
User.roles
This gives me the roles assigned to the user, I can then access the journal model like so:
journals = user.roles.first.journals
This gets me the journals associated with the user based on the roles. I want to be able to access the journals like so user.journals
In my user model I have tried this:
def journals
self.roles.collect { |role| role.journals }.flatten
end
This gets me the journals in a flatten array but unfortunately I am unable to access anything associated with journals in this case, e.g in the journals model it has:
has_many :items
When I try to access user.journals.items it does not work as it is a flatten array which I am trying to access the has_many association.
Is it possible to get the user.journals another way other than the way I have shown above with the collect method?
Hope you guys understand what I mean, if not let me know and ill try to explain it better.
Cheers
Eef
If you want to have user.journals you should write query by hand. As far as I know Rails does has_many :through associations (habtm is a kind of has_many :through) one level deep. You can use has_many with finder_sql.
user.journals.items in your example doesn't work, becouse journals is an array and it doesn't have items method associated. So, you need to select one journal and then call items:
user.journals.first.items
I would also modify your journals method:
def journals
self.roles(:include => :journals).collect { |role| role.journals }.flatten.uniq
end
uniq removes duplicates and :inlcude => :journals should improve sql queries.
Similar question https://stackoverflow.com/questions/2802539/ruby-on-rails-join-table-associations
You can use Journal.scoped to create scope with conditions you need. As you have many-to-many association for journals-roles, you need to access joining table either with separate query or with inner select:
def journals
Journal.scoped(:conditions => ["journals.id in (Select journal_id from journals_roles where role_id in (?))", role_ids])
end
Then you can use user.journals.all(:include => :items) etc

Resources