Rails activerecords with nested includes - ruby-on-rails

I have the following models:
class BusinessProcess < ActiveRecord::Base
has_many :todos
end
class Todo < ActiveRecord::Base
has_one :row
end
class Row < ActiveRecord::Base
has_many :users
end
How can I count the number of rows in a BusinessProcess that has rows on a specific user?
Something like:
#businessProcess.todos.includes(XXX).where(users.id=?,1).count

#businessProcess.todos.includes(:row => :users).where("users.id=?",1).count

According to your associations, I'd rather go with just joining the tables like:
class Todo < ActiveRecord::Base
has_one :row
has_many: users, through: :row
scope :by_user_id, ->(user_id) {
joins(:users).where("users.id = ?", user_id)
}
end
and then:
#business_process.todos.by_user_id(1).count
Maybe you also could think of moving the where condition into a scope of Row, but that is more a responsibility thingie.
You also could read about ARel as an alternative: The N+1 problem and ARel.

Related

Rails .where statement with nested resources

I'm struggling with a .where statement in an index action.
In my Deals controller, i'd like to list all the deals where the bank of the current_user is participating.
Below are my models :
class User < ActiveRecord::Base
belongs_to :bank
end
class Deal < ActiveRecord::Base
has_many :pools
end
class Pool < ActiveRecord::Base
belongs_to :deal
has_many :participating_banks, dependent: :destroy
has_many :banks, through: :participating_banks
end
class ParticipatingBank < ActiveRecord::Base
belongs_to :pool
belongs_to :bank
end
Here is my Deals Controller Index action :
def index
#deals = Deal.all
end
I don't find any way to say : 'I only want to see a deal if this deal has, at least, one pool where the current_user.bank has been added'.
Any idea?
Many thanks :)
You should do inner join and query joined table for id. You can easily do it in Rails by:
def index
#deals = Deal.joins(pools: :banks).where(banks: { id: current_user.bank_id })
end

How to scope a join table?

I'm using filterrific gem, and need to scope the following.
I have 3 tables. And I would like scope the User model , that returns all university names.that all User belongs through Colleges. Colleges is the join table here. My models are as below (Rails 4.2):
class User < ActiveRecord::Base
has_many :colleges
has_many :universities, :through => colleges
# my current scope which is not working at all
scope :user_university, -> (user_university){joins(colleges: [ {university: :name}]).where("universities.name = ? ", user_university)}
end
class Colleges < ActiveRecord::Base
belongs_to :user
belongs_to :university
end
class University < ActiveRecord::Base
has_many :colleges
has_many :users, :through => colleges
end
I keep getting errors and I'm unsure how to scope a model through a join table.
Let try as this:
scope :user_university, -> (university_name) {
joins(colleges: :university).where(universities: {name: university_name})
}
I rename the parameter user_university to university_name btw, user_university is kind of confusing!

Is: grandparent.parents.children association chaining not correct in Rails 4?

I'm having trouble figuring out the proper way of retrieving all children of multiple parents through association chaining.
To simplify I have three models:
class Customer < ActiveRecord::Base
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
After creating a few objects I tired to use the example from rails guides (association basics: 4.3.3.4 includes):
Customer.first.invoices.line_items
It returns:
undefined method `line_items' for #<Customer::ActiveRecord_Associations_CollectionProxy
Is grandparent.parents.children not usable?
EDIT
I'm not searching for the grandparent.parents.first.children, but all children of all parents in the collection, rails guides state:
If you frequently retrieve line items directly from customers (#customer.orders.line_items),
As a valid operation, I would like to know if that is a mistake.
FINAL As stated in the comments of the selected answer: in ActiveRecord: scopes are chainable but associations are not.
The customer.invoices.line_items cannot work the way you want to, since the has_many always is linked to a single record. but you can achieve what you want (if I understand correctly) using has_many through
as follows:
class Customer < ActiveRecord::Base
has_many :invoices
has_many :line_items, through: :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
and now you can write:
customer.line_items
and it will return all line_items which are connected to a customer's invoices.
Customer.first.invoices.first.line_items
Or if you want all of the data together, you can do something like:
results = Customer.first.invoices.includes(:line_items)
Then you may access data with no DB call, by looping results. For first data ex: results.first.line_items
Hope it helps!
Customer.first.invoices will return an collection (like an array) of invoices. The line_items method isn't defined for a collection, but its defined for an invoice. Try Customer.first.invoices.first.line_items
EDIT - If you always want the orders to include the line items, you can just do:
class Customer < ActiveRecord::Base
has_many :orders, -> { includes :line_items }
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end

Activerecord where condition with join without table name prefix

I have 2 tables. I use table prefix x_.
User (table x_users)
Comment (table x_comments)
I want to find out total count after inner join.
This query works fine.
User.joins(:comments).where(x_comments: {something: 1}).count
How can I remove x_ from where condition to make this call generic?
Models
class User < ActiveRecord::Base
has_many :comments, dependent: :destroy
end
class Comment < ActiveRecord::Base
attr_accessible :something
belongs_to :user
end
As #BroiSatse already mentioned, You can use ActiveRecord::Base.table_name to set the table name explicitly in a model and to get the table name in a query for genericity.
You query would be:
User.joins(:comments).where(Comment.table_name: {something: 1}).count
Setting a table name explicitly:
class Comment < ActiveRecord::Base
self.table_name = "x_comments"
end
You can override the table_name method like this:
class Comment < ActiveRecord::Base
def self.table_name
"x_" + super
end
end
Comment.table_name # => "x_comments"
Consider writing your conditions as scopes and let ActiveRecord handle the table aliasing for you.
class User < ActiveRecord::Base
has_many :comments, dependent: :destroy
def self.for_comment_something(foo)
joins(:comments).
merge(Comment.for_something(foo))
end
end
class Comment < ActiveRecord::Base
attr_accessible :something
belongs_to :user
def self.for_something(foo)
where(something: foo)
end
end
Documentation for ActiveRecord::Relation#merge is here.
Put it all together like
User.for_comments_something(1).count

Display categories that have posts in Rails 4

Trying to display a list of categories that have posts. Sounds simple but i'm a little stuck.
Similar to this
ActiveRecord find all parents that have associated children
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :category
end
Can you please help. I tried named scope but breaks the app.
You could either use a scope like this
scope :with_posts, -> { includes(:posts).where("posts.id IS NOT NULL") }
or using counter_cache
class Category < ActiveRecord::Base
has_many :posts
scope :with_posts, -> { where("posts_count > ?", 0) }
end
class Post < ActiveRecord::Base
belongs_to :category, counter_cache: true
end
Note that you have to add a posts_count integer field to the categories table for this to work. It is also advisable to save all your categories the first time to populate this field.

Resources