Activerecord where condition with join without table name prefix - ruby-on-rails

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

Related

Possible to alias has many associations with the same alias with different models?

I have an Article model that can have many different types of content blocks. So an Article can have many HeadingBlocks and ParagraphBlocks like this:
class Article < ApplicationRecord
has_many :heading_blocks
has_many :paragraph_blocks
end
class HeadingBlock < ApplicationRecord
belongs_to :article
end
class ParagraphBlock < ApplicationRecord
belongs_to :article
end
I want to be able to alias both HeadingBlock and ParagraphBlock with the same name (blocks) so if I have an instance of an article, I can do something like this:
#article.blocks // returns all heading blocks and paragraph blocks associated
Is this possible in Rails? If so, could you please provide an example on how to alias multiple models in a has many association using the same name?
Thank you.
You can have a method that returns an array of blocks:
class Article < ApplicationRecord
has_many :heading_blocks
has_many :paragraph_blocks
# NOTE: returns an array of heading and paragraph blocks
def blocks
heading_blocks + paragraph_blocks
end
end
You can reorganize the relationships to have a polymorphic association:
class Article < ApplicationRecord
has_many :blocks
end
# NOTE: to add polymorphic relationship add this in your migration:
# t.references :blockable, polymorphic: true
class Block < ApplicationRecord
belongs_to :article
belongs_to :blockable, polymorphic: true
end
class HeadingBlock < ApplicationRecord
has_one :block, as: :blockable
end
class ParagraphBlock < ApplicationRecord
has_one :block, as: :blockable
end
If you can merge HeadingBlock and ParagraphBlock into one database table:
class Article < ApplicationRecord
has_many :blocks
end
# id
# article_id
# type
class Block < ApplicationRecord
belongs_to :article
# set type as needed to `:headding` or `:paragraph`
end
# NOTE: I'd avoid STI; but this does set `type` column automatically to `ParagraphBlock`
# class ParagraphBlock < Block
# end

Rails activerecords with nested includes

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.

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

Rails 4, Active Record, create order scope on belongs_to for has_many_through

I have the following models set up:
class Location < ActiveRecord::Base
has_many :location_parking_locations
has_many :parking_locations, through: :location_parking_locations
end
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
end
class ParkingLocation < ActiveRecord::Base
has_many :location_parking_locations
has_many :locations, through: :location_parking_locations
end
The LocationParkingLocation has an integer field called upvotes. I would like to create a 'by_votes' scope that I can add to a query to order the results by this upvotes field. Where and how do I define this scope, so that I can call it like this:
location.parking_locations.by_votes
I can't define it like this, because then it's not a valid method on parking_locations:
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
scope :by_votes, -> { order("upvotes DESC") }
end
Should it be defined in the 'ParkingLocation' class? If so, how do I tell it that I want to order by a field on the location_parking_locations table?
I think you might be able to use merge here.
You can leave your scope in the LocationParkingLocation class, and the result would look like:
location.parking_locations.merge(LocationParkingLocation.by_votes)
I just read a little about it in this blog post.

Filtering results of a nested association based on the count of a deeper association

I have the following models and associations:
class ClassProfile < ActiveRecord::Base
has_many :enrollments, dependent: :destroy
has_many :student_profiles, through: :enrollments
class Enrollment < ActiveRecord::Base
belongs_to :student_profile
belongs_to :class_profile
class StudentProfile < ActiveRecord::Base
has_one :enrollment, dependent: :destroy
has_one :class_profile, through: :enrollment
has_many :relationships
has_many :parent_profiles, through: :relationships
class Relationship < ActiveRecord::Base
belongs_to :student_profile
belongs_to :parent_profile
class ParentProfile < ActiveRecord::Base
has_many :relationships
has_many :student_profiles, through: :relationships
What I want to do is define a method like the one below for ClassProfile
class ClassProfile < ActiveRecord::Base
...
def orphans
#return a collection of all student_profiles where
#there are no parent_profiles associated
#(i.e. parent_profiles.count = 0 or parent_profiles.empty? = true
end
I'd like to do this, if possible, with a single statement where I don't have to write a loop that manually queries each student_profile. Is there a way to do this, and if so, what is it?
Update
To clarify: I do have a method in StudentProfile as shown below:
class StudentProfile < ActiveRecord::Base
def child?
self.relationships[0].present?
end
And so I'd like to use a method like this:
class ClassProfile < ActiveRecord::Base
...
def orphans
self.student_profiles.where( child? == false )
end
That is, a single statement that returns the proper collection. But this is not a valid use of where and throws an error. Which makes sense because as far as I can tell, the where method on an associated model only works on fields, not methods. Anyway, this is the kind of thing I'm looking for, only something that is actually valid.
Maybe you can do a join for this. In the ClassProfile
def orphans
self.joins(student_profiles: :relationships)
end
The join here is an inner join. Students with no relationships will not be joined here. Moreover, maybe you can try a better name for your methods. Your naming is quite ambiguous.
I figured this out. Here's the code that has the desired behavior:
def orphans
student_profiles.reject { |s| s.child? }
end
I believe this needs to iterate through all the student profiles (as opposed to something that works at the query level), but it gets the job done.

Resources