Reputation: 117615
I have (simplified) a data model, that looks roughly like the following. E.g. a key-value table called "targets" that is used by "tasks".
class Target < ActiveRecord::Base
belongs_to :task
end
class Task < ActiveRecord::Base
has_one :name_target, -> { where(name: "name") },
class_name: "Target"
has_one :version_target, -> { where(name: "version") },
class_name: "Target"
end
For performance reasons, I would like to avoid making individual requests for each relation. Currently, the following code will trigger 3 individual SQL-requests:
t = Task.find(1) # SELECT * FROM tasks WHERE id = $1
t.name_target.value # SELECT * FROM targets WHERE task_id = $1 AND name = "name"
t.version_target.value # SELECT * FROM targets WHERE task_id = $1 AND name = "version"
What I would like to do, is preload ALL targets and then have AR reuse this for the has_one
relations. So, reducing the number of queries from 3 to 2.
Something like this:
class Task < ActiveRecord::Base
has_many :targets
has_one :name_target, -> { where(name: "name") },
class_name: "Target"
has_one :version_target, -> { where(name: "version") },
class_name: "Target"
end
t = Task.includes(:targets).find(1) # SELECT * FROM tasks WHERE id = $1
# SELECT * FROM targets WHERE task_id = $1
t.name_target.value # use preload
t.version_target.value # use preload
Except that doesn't work.
Any ideas how I could achieve what I want?
Upvotes: 1
Views: 43
Reputation: 102368
What I would like to do, is preload ALL targets and then have AR reuse this for the has_one relations. So, reducing the number of queries from 3 to 2.
You can't really do that with associations as ActiveRecord has no concept of different associations being linked to each other in that way. You could use enumerable to iterate through the loaded association:
Task.eager_load(:targets).find(1).targets.detect { |t| t.name == "name" }
But you're kind of approaching the problem wrong from the get go. To create one-to-one associations you want instead to add name_target_id
and version_target_id
foreign key columns to the tasks
table.
class AddTargetsToTasks < ActiveRecord::Migration[5.2]
def change
add_reference :tasks, :name_target, foreign_key: { to_table: :targets }
add_reference :tasks, :version_target, foreign_key: { to_table: :targets }
end
end
And use belongs_to
.
class Task < ActiveRecord::Base
has_many :targets
belongs_to :name_target, class_name: "Target"
belongs_to :version_target, class_name: "Target"
end
You then need to load each association to avoid further queries:
Task.eager_load(:targets, :name_target, :version_target)
Upvotes: 1