How can I give an alias name for e.g. includes()
?
Following is given:
Here some examples:
FIRST CASE (more STI associations)
Project.all.includes(:students, :teachers).order('teachers_projects.name ASC') # order on teachers
Project.all.includes(:students, :teachers).order('users.name ASC') # order on students
Rails uses automatically alias name teachers_projects
for :teachers
in the SQL. How can I overwrite this, so that I can use alias name teachers
instead of teachers_projects
in the SQL? :students
gets alias name users
.
This examples fails:
Project.all.includes(:students, :teachers).order('teachers.name ASC')
Project.all.includes(:students, :teachers).order('students.name ASC')
Project.all.includes(:students, :teachers).order('students_projects.name ASC')
SECOND CASE (one STI association)
If I use only :students
(without :teachers
) in method includes()
, Rails uses name alias of the STI base class name users
(without _projects
attached) for :students
:
Project.all.includes(:students).order('users.name ASC') # order on students
This examples fails:
Project.all.includes(:students).order('students.name ASC')
Project.all.includes(:students).order('students_projects.name ASC')
QUESTION
Might exist something like:
Project.all.includes(:students).alias(students: :my_alias)
RAILS ALIAS TRACKER
https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/associations/alias_tracker.rb#L59
TESTING APP
https://gist.github.com/phlegx/add77d24ebc57f211e8b
https://github.com/phlegx/rails_query_alias_names
The moral is, use joins when you want to do conditional set operations and use includes when you are going to be using a relation on each member of a collection.
ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending. Edit: as Mike points out, in this case ActiveRecord is a module... ActiveRecord is defined as a module in Rails, github.com/rails/rails/tree/master/activerecord/lib/…
Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.
Arel does actually have an alias method.
student_alias = Student.arel_table.alias(:my_student_table_alias)
caveat: this will require you to use even more handcrafted Arel and do the join manually. And the joins in Arel can get a bit complicated if you're not used to them.
student_alias_join = student_alias.create_on(
Project.arel_table[:primary_key].eq(student_alias[:project_id])
)
Project.joins(
Project.arel_table.create_join(
student_alias, student_alias_join, Arel::Nodes::OuterJoin
)
).order(...)
Something like this should do it.
Of course putting this into some Class method with :my_student_table_alias
as parameter would make it more tidy and reusable as this would look a bit messy in a controller.
I'm going to take another approach to this issue: instead of trying to control the alias names on your queries with an .alias
method, I'll let Rails / Arel handle that and just look the correct table name (aliased or not) up whenever there is need for it within a scope.
Add this helper method to your model, that you'd be able to call from an scope to know if the scope is being used within a JOIN
that has the table name aliased (multiple joins on the same table), or if on the other hand the scope has no alias for the table name.
def self.current_table_name
current_table = current_scope.arel.source.left
case current_table
when Arel::Table
current_table.name
when Arel::Nodes::TableAlias
current_table.right
else
fail
end
end
This uses current_scope
as the base object to look for the arel table. I'm calling source
on that object to obtain an Arel::SelectManager
that in turn will give you the current table on the #left
. There are two options there: either you have there an Arel::Table
(no alias, table name is on #name
) or you have an Arel::Nodes::TableAlias
with the alias on its #right
.
Now you only need to use that on your order
statements (untested):
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Related questions:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With