Rails efficient way to find user with only one shareholder - ruby-on-rails

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

Related

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

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

Include a record by default in any association

I have a Client Model as below:
class Client < ActiveRecord::Base
has_many :custodians,:dependent => :destroy
I have a Custodian Model as below:
class Custodian < ActiveRecord::Base
belongs_to :client
In my custodians table I have record with id = 0 , name = 'N/A' that I want to include in all my collection_selects irrespective of the client_id.
e.g for client_id = 10 I want the following in collection_select
Custodian.where('client_id = 10 or client_id = 0')
I know I can do it in my views but I have too many views so it is not practical. Plus I want a more DRY method on either Custodian model or associations. I tried default_scope on Custodian model but could not get it to work.
Basically I am looking for way to always include custodian with id=0 in each association and collection_select.
You can't do what you want using a has_many and belongs_to approach. To implement a belongs_to relationship, the Custodian record has to have a single client_id field. Your logic requires that the custodian_id=0 record belong to many Client records, so it would have to have many client_id fields, but it can only have one. See the Rails Guides-Active Record Associations-The belongs_to Association (http://guides.rubyonrails.org/association_basics.html)
You can accomplish what you want by using a has_and_belongs_to_many relationship. By making both the Custodian and Client models has_and_belongs_to_many to each other, you will be able to have the custodian_id=0 record belong to many Client records and all the other Custodian records will only belong to one client (even though they could belong to many, your program logic must only allow them to belong to one.) See the has_and_belongs_to_many section of the above Rails Guide. To be clear, here is how your models would look:
class Client < ActiveRecord::Base
has_many_and_belongs_to_many :custodians
end
class Custodian < ActiveRecord::Base
has_many_and_belongs_to_many :client
end
Also, because of your special case on custodian_id=0, you will need to establish the look-up table record for the custodian_id=0 record relationship using an active_record callback (probably before_validation or before_create) when you create a new Client record.
Similarly, you will need to implement your own :dependent => :destroy functionality using the before_destroy callback to preserve the custodian_id=0 record and delete all the other associated Custodian records. You'll also have to destroy the corresponding look-up table entries.
This sounds like a lot of work, but if you absolutely must have the custodian_id=0 record associated with every Client, this is the only way I can see it being done. You may want to evaluate it this is really necessary. There may be other program logic that could allow you to get to similar results without going through this process.
You could use an instance or class method:
#app/models/client.rb
Class Client < ActiveRecord::Base
has_many :custodians,:dependent => :destroy
def inc_zero(id)
where("client_id = ? OR client_id = 0", id)
end
def self.inc_zero_custodians(id)
joins(:custodians).where("client_id = ? OR client_id = 0", id)
end
end
#-> Client.custodians.inc_zero(10)
#-> Client.inc_zero_custodians(10)

Rails queries that consist of counts on associations, preferably with scopes

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

RoR: How to load the related record in a one-to-one relationship

I have the following:
class User < ActiveRecord::Base
has_one :subscription
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
The User has a subscription_id and thus can have only one subscription (which is what I want).
Which works fine, but now I do:
#users = User.find(:all)
and I want all the subscriptions to be included.
I tried:
#users = User.find(:all, :include=>[:subscription]) # include subscription
But that would like the subscription table to have a user_id (SQLite3::SQLException: no such column: subscriptions.user_id: SELECT "subscriptions".* FROM "subscriptions" WHERE ("subscriptions".user_id = 2)).
Which is (ofcourse) not what I want.
I am new at RoR and I couldn't find a good example of this case in the books I have nor on the web.
I think you have your associations the wrong way round on the model objects. You should have
class User < ActiveRecord::Base
belongs_to :subscription
end
class Subscription < ActiveRecord::Base
has_one :user
end
belongs_to should be used on the side of the association that defines the foreign key (in this case subscription_id). Semantically this probably looks a bit odd, but that's because in this case rails would kind of expect a user_id to be on the subscriptions table instead of the other way round as you have it.
After that
User.find(:all, :include=>[:subscription])
Should work fine
First of all if the user has foreign key (subscription_id) then it should have belongs_to not the other way around. As the Rails docs says for has_one method:
"This method should only be used if the other class contains the foreign key. If the current class contains the foreign key, then you should use belongs_to instead"
(taken from: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001834)
Second, in your example you tried to find User and include user. You need to do this:
#users = User.find(:all, :include=>[:subscription])

Resources