我正在尝试为我的 EventsLog 模型创建一个范围,它看起来类似于 EventsLog.with_values({"value_name"=> "value", "other_value_name"=> "other_value"})
。
其结果将是 EventsLog 记录,该记录对于散列中的每个键值对都有一个关联的 EventsLogValue。
这是我必须处理的。 定义如下所示的两个表:
--table for tracking events
CREATE TABLE events_log(
id INT PRIMARY KEY IDENTITY(1,1),
event_name VARCHAR(25), --name of the event
created_at DATETIME
);
--table for tracking the values corresponding to the event
CREATE TABLE events_log_values(
id INT PRIMARY KEY IDENTITY(1,1),
event_id INT,
value VARCHAR(255),
value_name VARCHAR(25),
);
从这两个表中,两个模型看起来像:
class EventsLog < BaseAPIDatabase
self.table_name = "events_log"
self.primary_key = "id"
has_many :events_log_values, :foreign_key => "event_id", :primary_key => "id", :class_name => "EventsLogValue", :autosave => true
scope :since, ->(since){ where("created_at > ?", since)}
scope :named, ->(event_name){ where(:event_name => event_name) }
def values
events_log_values.inject({}) do |hsh, v|
hsh.merge({v.value_name => v.value})
end
end
end
class EventsLogValue < BaseAPIDatabase
self.table_name = "events_log_values"
self.primary_key = "id"
end
到目前为止,我的方法是尝试创建一个函数,该函数返回一个事件记录关系,该关系一次应用一个键值对,然后再添加一个范围(或者可能只是一个返回关系的类方法)它为我链接了它们(类似于 scope :with_values, ->(values){values.inject(self){|slf, (k, v)| slf.with_value(k, v)} }
)。
最初我尝试将 with_value 实现为一个相当标准的范围,scope :with_value, ->(val_name, val){ eager_load(:events_log_values).where(:events_log_values => {:value_name => val_name, : value => val}) }
,它本身工作正常,但当链接时会导致单个连接,并在连接值上有多个条件。
决定通过将值表与每个条件的别名连接起来来解决这个问题;我的新方法是在我的 with_value 函数中定义一个 has_many 关联,然后 eager_load 该关联并根据每个新关联添加一个 where 条件:
def self.with_value(val_name, val)
has_many val_name.to_sym, ->(){ where(:value_name => val_name) }, :foreign_key => "event_id", :primary_key => "id", :class_name => "EventsLogValue"
res = eager_load(:events_log_values)
res.eager_load(val_name.to_sym).where("#{val_name.pluralize}_events_log" => {:value => val})
end
这实际上工作得很好,但有一些问题。第一个是我很难知道在 where 条件下关联的名称是什么。第二个(也是更大的问题)是我的值函数现在只有任何 value_names 没有为它们建立关联。
这是由多个 has_manys 生成的一些 sql,可能有助于说明我正在尝试做的事情:
EventsLog.with_values("hello" => "world", "foo" => "bar").to_sql
SELECT ...
FROM [events_log]
LEFT OUTER JOIN [events_log_values] ON [events_log_values].[event_id] = [events_log].[id]
LEFT OUTER JOIN [events_log_values] [hellos_events_log] ON [hellos_events_log].[event_id] = [events_log].[id] AND [hellos_events_log].[value_name] = 'hello'
LEFT OUTER JOIN [events_log_values] [foos_events_log] ON [foos_events_log].[event_id] = [events_log].[id] AND [foos_events_log].[value_name] = 'foo'
WHERE [hellos_events_log].[value] = 'world' AND [foos_events_log].[value] = 'bar'
我怎样才能得到一条记录,该记录有多个关联记录满足多个不同的条件?
最佳答案
这是我提出问题后得出的答案。它使用 arel 为每个值生成带有别名的 sql 连接,并为每个值生成一个 where 条件。
这不是最干净的东西,但它似乎完成了工作。
def self.with_values(values)
el = EventsLog.arel_table
arel_joins = el
arel_wheres = []
values.each do |k, v|
ev = EventsLogValue.arel_table.alias("#{k}_join")
arel_joins = arel_joins.join(ev).on(el[:id].eq(ev[:event_id]).and(ev[:value_name].eq(k)))
arel_wheres << ev[:value].eq(v)
end
arel_wheres.inject(EventsLog.joins(arel_joins.join_sources)){|rel, con| rel.where(con)}
end
附注我想我在某处读到 Model.arel_table 没有记录并且不应该使用?谨慎的做法是改用 Arel::Table.new('table_name')。
关于ruby-on-rails - activerecord如何查询关联的多个条件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42795757/