I've been making a search page for my service, and it includes several association based search parameters, I can think of a few messy long sql mess, but would prefer some cleaner approaches, as a sample,
class Person < ActiveRecord::Base
has_many :friends
end
Friend has an attribute that indicates the state of the friendship, something like friend_type
class Friend < ActiveRecord::Base
belongs_to :person
end
The search form would consist of many parameters, two of them being searching for people who have more than a certain number of friends, and how many people have friends that have friend_type set to, say "bff".
What I would like to do is have some scope methods in the model, and in the Controller be able to do this,
some model scopes in Person like this,
scope :by_friends_count_greater_than, lambda { |value| joins(:friends).where( #count friends#, value ) if value }
scope :by_bff_count_greater_than, lambda { |value| joins(:friends).where( ##count friends with bff status## , value ) if value }
and call them in the controller as so,
#people = Person.by_friends_count_greater_than(params[:query][:friends_count])
.by_bff_count_greater_than(params[:query][:bff_count])
I have been trying out squeel, which seems to be a very nice asset, considering it can call methods in the query. Is it possible to have search queries done in this fashion?
If there is a better way to approach this, that would be very appreciated.
You might be interested in counter_cache to have simpler queries.
It will auto increment a counter to each Person model.
http://railscasts.com/episodes/23-counter-cache-column
You have to add a friends_count column to your Person model
and specify the counter_cache on the belongs_to
class Friend < ActiveRecord::Base
belongs_to :person, counter_cache: true
end
Related
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
Is it possible to make a scope for a polymorphic model in the following case?
I have a polymorphic model named Mutations.
class Mutation < ApplicationRecord
belongs_to :mutationable, :polymorphic => true
end
A Mutation belongs to both models TimeRegistration and SickRegistration. A TimeRegistration and a SickRegistration (Mutationable) belongs_to a User.
class SickRegistration < ApplicationRecord
belongs_to :user
has_many :mutations, as: :mutationable
end
class TimeRegistration < ApplicationRecord
belongs_to :user
has_many :mutations, as: :mutationable
end
I want to create a scope for the Mutation whereby i can retrieve a collection of Mutations by a given user name. I have more scopes already on the Mutation model, so this one must be joined with the other used scopes (used for filtering).
So something like this on the Mutation Model:
scope :with_name, -> (name) { joins(mutationable: :user).where('users.name = ?', name) }
This won't work. I've also tried to make a delegate on the Mutation model and to make a custom SQL Query with multiple joins on the mutationable models, but without success. I think there must be an (more easy) way to do this, but i can't find any good examples or ansewers for this problem.
Please help. Thanks in advance!
******TRY THIS*****
Mutation.rb
belongs_to :sick_registation, ->{where(mutations: {mutationable_type: 'SickRegistation'})},
foreign_key: 'mutationable_id'
belongs_to :time_registation, ->{where(mutations: {mutationable_type: 'TimeRegistation'})},
foreign_key: 'mutationable_id'
scope :with_name, ->(name){
joins(sick_registation: :user).where(user:{name: name}) +
joins(time_registation: :user).where(user:{name: name})
}
Explanation:
There is no direct relation between your polymorphic relation and user.
So, I am joining the results of individual associated i.e sick and time.
Try
scope :with_name, -> (name) { where(mutationable: User.where(name: name)) }
edited. added )
Edit2:
My bad for my "wrong quick solution" due to not reading carefully enough, but I see the question has been edited and it is more clear now.
Knowing what you want to achieve I would suggest a different approach. Mutable should not be hardcoded looking at specific models because it will limit the flexibility provided by the polymorphic associations and will break 'Law of Demeter'
To get a collection of mutations of a particular set of XXXRegistrations I would do:
a. Use STI so the queriable XXXRegistrations, extend a Registration model. Add a scope to filter by user.
SickRegistration < Registration
b. Query Registration for user and then get the mutations
Registration.for_user(user).joins(:mutations)
Im having trouble figuring out how to write a multi-layer sort using a scope method in my model, which can sort through the model's attributes, as well as its related child models' attributes?
To put more concretely, I have the following models, each a related child of the previous one (I excluded other model methods and declarations for brevity):
class Course < ActiveRecord::Base
has_many :questions
# would like to write multi-layer sort here
end
class Question < ActiveRecord::Base
belongs_to :course, :counter_cache => true
has_many: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question, :counter_cache => true
end
I would like to sort courses first by questions_count (through my counter_cache), then by answer_count, and lastly by created_at, and was wondering how I could string everything together into a single scope method to put in my Course model.
Thanks.
As seen here (creating a scope with a joined model) : problem: activerecord (rails3), chaining scopes with includes
And here (sorting with multiple columns) : Ruby on Rails: how do I sort with two columns using ActiveRecord?
And finally here (sorting by associated model) : Rails 3. sort by associated model
You may achieve this like so :
scope :complex_sorting, lambda {
joins(:questions)
.order('questions_count DESC, question.answers_count DESC, created_at DESC')
}
I think I need something akin to a rails eager loaded query with a limit on it but I am having trouble finding a solution for that.
For the sake of simplicity, let us say that there will never be more than 30 Persons in the system (so Person.all is a small dataset) but each person will have upwards of 2000 comments (so Person.include(:comments) would be a large data set).
Parent association
class Person < ActiveRecord::Base
has_many :comments
end
Child association
class Comment < ActiveRecord::Base
belongs_to :person
end
I need to query for a list of Persons and include their comments, but I only need 5 of them.
I would like to do something like this:
Limited parent association
class Person < ActiveRecord::Base
has_many :comments
has_many :sample_of_comments, \
:class_name => 'Comment', :limit => 5
end
Controller
class PersonController < ApplicationController
def index
#persons = Person.include(:sample_of_comments)
end
end
Unfortunately, this article states: "If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects"
Is there any good way around this? Or am I doomed to chose between eager loading 1000s of unneeded ActiveRecord objects and an N+1 query? Also note that this is a simplified example. In the real world, I will have other associations with Person, in the same index action with the same issue as comments. (photos, articles, etc).
Regardless of what "that article" said, the issue is in SQL you can't narrow down the second sql query (of eager loading) the way you want in this scenario, purely by using a standard LIMIT
You can, however, add a new column and perform a WHERE clause instead
Change your second association to Person has_many :sample_of_comments, conditions: { is_sample: true }
Add a is_sample column to comments table
Add a Comment#before_create hook that assigns is_sample = person.sample_of_comments.count < 5
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.