Search where HABTM is in another model's HABTM - ruby-on-rails

I have two models, Subcontractor:
class Subcontractor < ActiveRecord::Base
has_and_belongs_to_many :trades
end
And Bid:
class Bid < ActiveRecord::Base
has_and_belongs_to_many :trades
end
I'm trying to find all subcontractors who have trades that match the trades of a particular bid. I've tried something along the lines of Subcontractor.where(trade_ids: bid.trade_ids) but that doesn't seem to work.

If you pass an array to an ActiveRecord query, ActiveRecord is smart enough to know that you mean all items IN the array.
Have you tried
Subcontractor.where(trade_ids: bid.trades.pluck(:id))

Here is how I finally did the query:
Subcontractor.joins(:trades).where('trades.id' => bid.trade_ids).uniq

You can do it in this way also
Subcontractor.where("trade_id in (?)", bid.trades.pluck(:id))

Related

ActiveRecord: Reference attribute from associated model in where statement

I've got two models with a has_many / has_many relationship. I have a variable exp_ids which is an array of integers representing the id's of some ExperienceLevel records. I need to write a query that will select all JobDescriptions that have an ExperienceLevel with one of those ids.
The query must work on an existing ActiveRelation object called job_descriptions, which is being passed through some flow controls in my controller to filter the results based on my params.
I've tried these queries below and some other variations, but with little success. As far as I can tell, ActiveRecord thinks that experience_levels is an attribute, which is causing it to fail.
job_descriptions.where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels.id: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where("experience_levels.id IN exp_ids")
job_descriptions.includes(:experience_levels).where("experience_levels.id = ?", exp_ids).references(:experience_levels)
Here are my models:
class JobDescription < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :experience_levels, through: :job_description_experience_levels
end
class JobDescriptionExperienceLevel < ActiveRecord::Base
belongs_to :job_description
belongs_to :experience_level
end
class ExperienceLevel < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :job_descriptions, through: :job_description_experience_levels
end
I'm not sure if what I want to do is even possible. I've used a similar approach for another job_description filter where I selected the company_id, but in the case, company_id was an attribute of JobDescription.
Try this:
job_descriptions.joins(:job_description_experience_levels).where(job_description_experience_levels: { experience_level_id: exp_ids })
job_descriptions.joins(:experience_levels).where(experience_levels: {id: exp_ids})
Try this one. Note the lack of plural on the experience level.id
job_descriptions.includes(:experience_levels).where("experience_level.id = ?", exp_ids).references(:experience_levels)

Applying conditions on has_many relationship

I'm having trouble retrieving some data from a Postgre database with rails 4.1.8
Let's considere two models with a has_many relationship
class Post < ActiveRecord::Base
has_many :comments
end
and
class Comment < ActiveRecord::Base
belongs_to :post
end
Comments have a state with approved or censured
I want to write a method in Post model self.all_comments_approved
I cannot figure out how to get only posts with all comments approved.
I would like to write something like this (not working example) :
def sef.all_comments_approved
joins(:comments).where("ALL(comments.state = 'approved')").references(:comments)
end
Thanks in advance for any help :)
You might try using joins with group and having statement, but relation you would get would be very fragile (you wouldn't be able to query it any further as easily as you wish - pluck would destroy it completely etc).
Way around this issue is to split it into two db calls:
def self.all_comments_approved
non_approved_ids = joins(:comments).where.not(comments: {state: 'approved'}).uniq.pluck(:id)
where.not(id: non_approved_ids)
end

Rails relationship based on common field

I've found quite a few topics on ActiveRecord has_many relationships, but I haven't found exactly what I am looking for.
I've got two tables that each have the column xyz_id. This id is a corresponding ID in an API service I'm subscribed to.
I want to have a has_many relationship through these corresponding ids in the corresponding columns on each table. Such that, if an item in table_1 has an xyz_id of "abcdefg", I can do something like table_1.relation and it will return all elements in table_2 with a matching xyz_id. I could leverage ActiveRecord throughout my code and utilize queries, but I think there has to be a better way. Any thoughts?
EDIT: I a word.
ActiveRecord lets you specify arbitrary fkeys when you define the relationship, like so:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts,
foreign_key: :xyz_id
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies,
foreign_key: :xyz_id
end
Source: http://guides.rubyonrails.org/association_basics.html

In Activerecord can you use a hash selector instead of a join query to find records through a belongs_to relation?

I have two models - "Part" belongs_to "Category"
In this scenario is it possible to take this query:
Part.joins(:category).where("categories.name = 'cars'").first
And create a hashed variation of it? I've seen stuff like this:
Part.where(categories: {name: 'cars'})
However the SQL it generates for the two are pretty different. If (big if) I'm not doing anything wrong here, then why does the hash'd version not work in this case? And what are the relationships in which it is intended to work in?
In case it helps here are my models:
Category Model:
class Category < ActiveRecord::Base
has_many :parts
end
Parts Model:
class Part < ActiveRecord::Base
belongs_to :category
end
You've missed join() and first() methods in the second example. Use:
Part.joins(:category).where(categories: { name: 'cars' }).first

Rails app using STI -- easiest way to pull these records?

I'm learning my way around Rails and am working on a sample app to keep track of beer recipes.
I have a model called Recipe which holds the recipe name and efficiency.
I have a model called Ingredient which is using STI - this is subclassed into Malt, Hop, and Yeast.
Finally, to link the recipes and ingredients, I am using a join table called rec_items which holds the recipe_id, ingredient_id, and info particular to that recipe/ingredient combo, such as amount and boil time.
Everything seems to be working well - I can find all my malts by using Malt.all, and all ingredients by using Ingredient.all. I can find a recipe's ingredients using #recipe.ingredients, etc...
However, I'm working on my recipe's show view now, and am confused as to the best way to accomplish the below:
I want to display the recipe name and associated info, and then list the ingredients, but separated by ingredient type. So, if I have a Black IPA # 85% efficiency and it has 5 malts and 3 hops varieties, the output would be similar to:
BLACK IPA (85%)
Ingredient List
MALTS:
malt 1
malt 2
...
HOPS:
hop 1
...
Now, I can pull #recipe.rec_items and iterate through them, testing each rec_item.ingredient for type == "Malt", then do the same for the hops, but that doesn't seem very Rails-y nor efficient. So what is the best way to do this? I can use #recipe.ingredients.all to pull all the ingredients, but can't use #recipe.malts.all or #recipe.hops.all to pull just those types.
Is there a different syntax I should be using? Should I using #recipe.ingredient.find_by_type("Malt")? Doing this in the controller and passing the collection to the view, or doing it right in the view? Do I need to specify the has_many relationship in my Hop and Malt models as well?
I can get it working the way I want using conditional statements or find_by_type, but my emphasis is on doing this "the Rails way" with as little DB overhead as possible.
Thanks for the help!
Current bare-bones code:
Recipe.rb
class Recipe < ActiveRecord::Base
has_many :rec_items
has_many :ingredients, :through => :rec_items
end
Ingredient.rb
class Ingredient < ActiveRecord::Base
has_many :rec_items
has_many :recipes, :through => :rec_items
end
Malt.rb
class Malt < Ingredient
end
Hop.rb
class Hop < Ingredient
end
RecItem.rb
class RecItem < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
recipes_controller.rb
class RecipesController < ApplicationController
def show
#recipe = Recipe.find(params[:id])
end
def index
#recipes = Recipe.all
end
end
Updated to add
I'm now unable to access the join table attributes, so I posted a new question:
Rails - using group_by and has_many :through and trying to access join table attributes
If anyone can help with that, I'd appreciate it!!
It's been a while since I've used STI, having been burned a time or two. So I may be skipping over some STI-fu that would make this easier. That said...
There are many ways of doing this. First, you could make a scope for each of malt, hops, and yeast.
class Ingredient < ActiveRecord::Base
has_many :rec_items
has_many :recipes, :through => :rec_items
named_scope :malt, :conditions => {:type => 'Malt'}
named_scope :hops, :conditions => {:type => 'Hops'}
...
end
This will allow you to do something line:
malts = #recipe.ingredients.malt
hops = #recipe.ingedients.hops
While this is convenient, it isn't the most efficient thing to do, database-wise. We'd have to do three queries to get all three types.
So if we're not talking a ton of ingredients per recipe, it'll probably be better to just pull in all #recipe.ingredients, then group them with something like:
ingredients = #recipe.ingredients.group_by(&:type)
This will perform one query and then group them into a hash in ruby memory. The hash will be keyed off of type and look something like:
{"Malt" => [first_malt, second_malt],
"Hops" => [first_hops],
"Yeast" => [etc]
}
You can then refer to that collection to display the items however you wish.
ingredients["Malt"].each {|malt| malt.foo }
You can use group_by here.
recipe.ingredients.group_by {|i| i.type}.each do |type, ingredients|
puts type
ingredients.each do |ingredient|
puts ingredient.inspect
end
end
The utility of STI in this instance is dubious. You might be better off with a straight-forward categorization:
class Ingredient < ActiveRecord::Base
belongs_to :ingredient_type
has_many :rec_items
has_many :recipes, :through => :rec_items
end
The IngredientType defines your various types and ends up being a numerical constant from that point forward.
When you're trying to display a list this becomes easier. I usually prefer to pull out the intermediate records directly, then join out as required:
RecItem.sort('recipe_id, ingredient_type_id').includes(:recipe, :ingredient).all
Something like that gives you the flexibility to sort and group as required. You can adjust the sort conditions to get the right ordering. This might also work with STI if you sort on the type column.

Resources