我正在使用Rails 5.我在我的控制器模型中有这个用于根据标准加载某个模型......
@results = MyObjectTime.joins(:my_object,
"LEFT JOIN user_my_object_time_matches on my_object_times.id = user_my_object_time_matches.my_object_time_id #{additional_left_join_clause}")
.where( search_criteria.join(' and '), *search_values )
.limit(1000)
.order("my_objects.day DESC, my_objects.name")
.paginate(:page => params[:page])
我的问题是,如何重写上述内容,以便@results数组同时包含" MyObjectTime"和任何潜在的" UserMyObjectTimeMatch"它链接到?
答案 0 :(得分:3)
你做不到。至少不使用ActiveRecord或Rails'默认界面。 ActiveRecord查询方法的设计方式使它们只返回调用Model的对象。
例如,如果您查询
MyObjectTime.joins(:time).where(parent_id: 5)
它仅返回MyObjectTime
的对象。但是,由于join
,可能还会获取关联time
中的记录,但不会返回。所以,你可以利用它。特别是当您使用includes
代替joins
时,会获取关联的模型,您可以通过引用关联记录/对象来使用它们。
构建结果对的说明
通过创建具有所需结果的哈希,可以轻松完成此操作。
例如,考虑具有Mark
关联的模型answer_sheet
。
您可以使用:answer_sheet
以includes
方式获取标记。 我在示例中提取了20个。
marks = Mark.limit(20).includes(:answer_sheet);
这会获取可以通过mark检索的answer_sheet,因此,以这种方式构建哈希
h = {}
marks.each do |mark|
h[mark.id] = {}
h[mark.id][:mark] = mark
h[mark.id][:answer_sheet] = mark.answer_sheet
end
现在,您的哈希通过mark.id键准备好mark
和answer_sheet
对象。
这只会在首次提取时最多执行两次查询,并且迭代不会触发任何进一步的查询。在我的系统中,只有两个必需的查询是(使用includes
)
SELECT "marks".* FROM "marks" LIMIT 20
AnswerSheet Load (0.9ms) SELECT "answer_sheets".* FROM "answer_sheets" WHERE "answer_sheets"."mark_id" IN (877, 3035, 3036, 878, 879, 880, 881, 561, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893)
您甚至可以使用标记对象本身作为键。然后构建过程变得更加简单
h = {}
marks.each do |mark|
h[mark] = mark.answer_sheet
end
现在,只要您想访问与answer_sheet
相关联的mark
,您就需要使用h[mark]
来获取它。
答案 1 :(得分:3)
我建议您搜索替代方案,但要了解您提供的信息并希望避免评论中提到的潜在10001查询,如果您有has_many' user_my_object_time_matches'您可以做的设置:
@results = MyObjectTime.joins(:my_object,
"LEFT JOIN user_my_object_time_matches on my_object_times.id = user_my_object_time_matches.my_object_time_id #{additional_left_join_clause}")
.where( search_criteria.join(' and '), *search_values )
.limit(1000)
.order("my_objects.day DESC, my_objects.name")
.paginate(:page => params[:page])
.includes(:user_my_object_time_matches)
.map{|t| [t, t.user_my_object_time_matches]}
答案 2 :(得分:2)
您可以使用ActiveRecord连接执行原始SQL查询,该连接允许您根据需要包含尽可能多的表中的列。您可以在一个查询中获取所有内容。您需要确保为不明确的列名称添加别名(例如,就像我在示例中为name
列所做的那样)
我不知道你的模特是什么样的,但这里有一个简单的父/兄弟示例来展示:
# testmigration.rb
class Testtables < ActiveRecord::Migration[5.0]
def change
create_table :parents do |t|
t.string :name
t.timestamps
end
create_table :siblings do |t|
t.string :name
t.references :parent
t.timestamps
end
end
end
# parent.rb
class Parent < ApplicationRecord
has_many :siblings
end
# sibling.rb
class Sibling < ApplicationRecord
end
> rails c
> Parent.new(name: "Parent A").save!
> Parent.new(name: "Parent B").save!
> Sibling.new(name: "Sibling 1 - Parent A", parent_id: 1).save!
> Sibling.new(name: "Sibling 2 - Parent A", parent_id: 1).save!
> Sibling.new(name: "Sibling 1 - Parent B", parent_id: 2).save!
> Sibling.new(name: "Sibling 2 - Parent B", parent_id: 2).save!
> Sibling.new(name: "Sibling 3 - Parent B", parent_id: 2).save!
> sql_query = "SELECT p.name as parent_name, p.created_at as parent_created, s.name as sibling_name, s.created_at as sibling_created FROM public.parents p INNER JOIN public.siblings s on s.parent_id = p.id;"
> result = ActiveRecord::Base.connection.execute(sql_query)
> result[0]['parent_name']
=> "Parent A"
> result[0]['sibling_name']
=> "Sibling 1 - Parent A"
> result[1]['parent_created']
=> "2017-03-04 18:31:54.661714"
答案 3 :(得分:1)
无法评论,所以要问这样。
问题是,你为什么要这样做?
为什么你认为使用关系对你不起作用?
# I already have some results pulled the way you did and I wanna use them
@results.each do |r|
self.foo(r, r.user_my_object_time_match);
end
#or if I really wanted a double array, then I could do
@results = MyObjectTime.<clauses>().collect { |mot| [mot, mot.user_my_object_time_match] }
如果这些不适合您,请说明问题所在,因为此时此问题看起来有XY问题(https://meta.stackoverflow.com/tags/xy-problem/info)
答案 4 :(得分:1)
您可以在此处使用eagerloading,而不是将整个结果合并到一个数组中,如:
@results = MyObjectTime.joins(:my_object,
"LEFT JOIN user_my_object_time_matches on my_object_times.id = user_my_object_time_matches.my_object_time_id #{additional_left_join_clause}")
.where( search_criteria.join(' and '), *search_values )
.limit(1000)
.order("my_objects.day DESC, my_objects.name")
.paginate(:page => params[:page]).includes(:user_my_object_time_matches)
一旦你使用了包含它就不会激发额外的查询。
@first_my_object_time = @results.first
@user_my_object_time_matches = @first_my_object_time.user_my_object_time_matches
如果你想要它在同一个数组中,你可以使用ActiveRecord Select方法直接从sql中选择:
@results = MyObjectTime.joins(:my_object,
"LEFT JOIN user_my_object_time_matches on my_object_times.id = user_my_object_time_matches.my_object_time_id #{additional_left_join_clause}")
.where( search_criteria.join(' and '), *search_values )
.limit(1000)
.order("my_objects.day DESC, my_objects.name")
.paginate(:page => params[:page]).select("my_object_time.*, user_my_object_time_matches.*").as_json
答案 5 :(得分:1)
如果没有关于模型或数据库的完整信息,我在下面做了一些假设。大多数情况下,通过AR模型中的正确关联和/或范围,您可以在不编写任何原始SQL的情况下完成:
class MyObjectTime < ApplicationRecord
has_many :my_objects, ->(args){ where(args) }
scope :top_1000, ->{ limit(1000) }
scope :order_by_my_objects, ->{ order(my_objects: { day: :desc, name: :asc }) }
end
class UserMyObjectTimeMatches < ApplicationRecord
belongs_to :my_object_time
end
MyObjectTime.my_objects(params[:search_args])
.order_by_my_objects.top_1000
.include(:my_object_time).paginate(page: params[:page])
如果我有完整的代码,我可以设置模型和测试 - 所以这段代码没有经过测试,很可能需要调整。