Applying conditions on has_many relationship - ruby-on-rails

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

Related

Rails efficient way to find user with only one shareholder

In my method I want to check if user has only one shareholder, if it so it should return true (later on it's used in if block). Basically it should reflect something like User.find_by(id: user.id).shareholder.count < 1 because it overloads the database with unnecessary queries (I have db with ~30k users).
What I was thinking is to create queries with where so I have this one:
def one_shareholder?(shareholder)
ShareholdersUser.where(user_id: shareholder.owner_id).
where.not(shareholder_id: shareholder.id).exists?
end
But I don't know how to implement query which will be counting if this user has only one shareholder. Should I use something like find_each ?
Edit:
user has_many :shareholder
shareholder belongs_to :owner
ShareholdersUser belongs_to :user and :shareholder
Maybe this can give you an hint. I used something similar in a project where I have these models:
class Company < ApplicationRecord
has_many :permits
end
and
class Permit < ApplicationRecord
belongs_to :company
end
For fetching companies having just one permit I used:
Company.joins(:permits).group('companies.id').having('count(company_id) = 1')
Maybe you can pluck the ids and use the array to check wether the company is in the array. For example:
ids_of_companies_having_one_permit = Company.joins(:permits).group('companies.id').having('count(company_id) = 1').pluck(:id)
Then check:
if ids_of_companies_having_one_permit.include? company.id ....
This is the thread I followed: Find all records which have a count of an association greater than zero
Firstly, if you are finding user from his ID then can directly use User.find(user.id) instead of User.find_by(id: user.id)
Secondly, As you mentioned in your question that you want either true/false for your if condition.
And as per your query I think you have user has_many shareholder association implemented.
So you can directly use User.find(user.id).shareholders < 1 in your if condition like below,
if User.find(user.id).shareholders.count < 1
#do something...
end
Note: I've used the plural of the shareholder in condition because we have has_many association

Rails active record query with multiple associations

I have tables called users, orders, and delivery_times that are linked using the following relationship.
For table User:
belongs_to :orders
For table orders:
belongs_to :delivery_times
I want to write a query on table users using a condition on table delivery_times as shown:
User.includes(order: :delivery_time).where("delivery_times.start < ?",Time.now)
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "delivery_times"
However I get an error. Can I use the RoR ORM to make this query work using includes, even though I know there is a solution using joins?
You will need a join for this kind of query, since you need the joint knowledge of the delivery_times table and the users table.
What includes actually does is it decides between preload and eager_load automatically and tries to always take the better one. In you case it will do an eager_load; have a look into this article.
For the error you get, I guess it yould result from starting with Users and not User:
User.includes(order: :delivery_time).where("delivery_times.start < ?",Time.now)
Everything else seems correct to me.
The better definition of relations between your models would be this:
So your classes would look like this:
class User < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :user
has_one :delivery_time
end
class DeliveryTime < ActiveRecord::Base
belongs_to :order
end
The query you are making doesn't make any sense? What is the result that you are expecting?
If you want to get order that their delivery time is a specific time you can use scopes:
class Order < ActiveRecord::Base
belongs_to :user
has_one :delivery_time
scope :ready_to_deliver, includes(:delivery_time).where("delivery_time.start < ? ", Time.now)
end
Then you can get orders that are ready to deliver like this:
ready_orders = Order.ready_to_deliver

Rails - how to find out if an ID is in the "has_many" relation

I have this structure of models:
class User < ActiveRecord::Base
has_many :groups
end
class Group < ActiveRecord::Base
belongs_to :user
end
and in the view, I would need to find out if the respective user is in the specific group - how to do that?
Is there any native Rails method that would do something like this:
<% if current_user.groups.IS_THIS_GROUP_ID_IN_USERS_GROUPD(#group.id)? %>
or do I need to write it by me? Or, what's the most time efficient way to find out this?
EDIT:
Sorry guys, I made a mistake - there's one more model, so the structure look like this:
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :groups
end
class Group < ActiveRecord::Base
belongs_to :user
end
So what I am trying to do is basically this:
<% if current_user.favorites.IS_THIS_GROUP_ID_IN_USERS_FAVORITES_GROUPS(#group.id)? %>
I am sorry one more time, I don't know how I could overlooked the Favorite model.
Thanks
I would go with:
current.user.groups.include?(#group)
or if you only have an id:
current.user.group_ids.include?(id)
How about current.user.groups.include?(#group)
You can also use exists?, which is done in the database.
current_user.groups.exists?(#group)
More information about exists? can be found in the Rails Active Record Querying Guide.
Try
If you're trying to see if the object has any groups, you may wish to use the .try method (although I'm not sure if this an accurate use case):
current_user.try(:groups)
--
Include
You may wish to try and use the include? method, as shown by spickerman. This will ping the array to see if a particular element exists inside (it's not ActiveRecord, so you'll have to use ids)
current_user.groups.include?(#group.id) #-> true / false

Ruby On Rails ActiveRecord 3 Way Join

I have 3 models:
class ProductLine < ActiveRecord::Base
has_many :specifications
has_many :specification_categories, :through => :specifications,
end
class Specification < ActiveRecord::Base
belongs_to :product_line
belongs_to :specification_category
end
class SpecificationCategory < ActiveRecord::Base
has_many :specifications
has_many :product_lines, :through => :specifications
end
Basically, we are showing the specifications as a subset of data on the product line page and we would like to do something like (example only, yes I'm aware of N+1):
Controller:
#product_line = ProductLine.find(params[:id])
#specification_categories = #product_line.specification_categories)
View:
#specification_categories.each do |specification_category|
...
specification_category.specifications.each do |specification|
...
end
end
The issue here is getting rails to filter the specifications by ProductLine. I've tried constructing queries to do this but it always generates a separate NEW query when the final association is called. Even though we aren't using the code above now (not a good idea here, since we could potentially run into an N+1 problem), I'd like to know if it's even possible to do a 3 way join with association filtering. Has anyone run across this scenario? Can you please provide an example of how I would accomplish this here?
Prevent the N+1 by altering your line to:
#specification_categories = #product_line.specification_categories).include(:specifications)
OR
Construct your own query using .joins(:association), and do the grouping yourself.

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