我正在尝试为我的EventsLog模型创建一个范围,它看起来像EventsLog.with_values({"value_name" => "value", "other_value_name" => "other_value"})
。
其结果将是对于散列中的每个键值对具有关联的EventsLogValue的EventsLog记录。
这是我必须要做的事情。 两个表的定义如下:
--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}) }
,它本身可以正常工作,但是当链接导致在连接值上有多个条件的单个连接时。
决定通过将values表与每个条件的别名连接来解决这个问题;我的新方法是在我的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
这实际上运作得很好,但有一些问题。第一个是我很难知道在哪里条件的名称将是关联。第二个(也是更大的问题)是我的值现在只有任何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'
我怎样才能获得一条记录,其中有几条相关记录符合几个不同的条件?
答案 0 :(得分:0)
这是我提出问题后能够提出的答案。它使用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
P.S。我想我在哪里读过Model.arel_table没有文档,不应该使用?使用Arel :: Table.new(&#39; table_name&#39;)代替可能是谨慎的。