activerecord如何查询关联的多个条件?

时间:2017-03-14 20:18:32

标签: ruby-on-rails activerecord

我正在尝试为我的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'

我怎样才能获得一条记录,其中有几条相关记录符合几个不同的条件?

1 个答案:

答案 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;)代替可能是谨慎的。