使用 OR(而不是 AND)组合两个命名范围

发布于 2024-08-06 03:03:52 字数 851 浏览 9 评论 0原文

我想找到所有主体为以下内容的Annotations

  • 等于“?”
  • 像“[?]”

最好的方法是什么?

如果可能的话,我想使用 SearchLogic ,但尽管 SearchLogic 允许您执行以下各项操作:

  • Annotation.body_equals('?')
  • Annotation.body_like('[?]')

并且您始终可以将它们链接在一起:Annotation.body_equals('?' ).body_like('[?]')

我不知道如何将它们与 OR 结合起来。

请注意,您可以组合命名范围与 OR if 他们的论点是相同的。 例如,我可以这样做:

 Annotation.body_equals_or_body_like('?')

但这没有帮助。

请注意,我不喜欢 SearchLogic,但对于不需要破坏其抽象的解决方案来说,这将是很棒的。

I want to find all Annotations whose bodies are either:

  • Equal to "?"
  • or
  • Like "[?]"

What's the best way to do this?

I would like to use SearchLogic if possible, but though SearchLogic allows you to do each of the following:

  • Annotation.body_equals('?')
  • Annotation.body_like('[?]')

and you can always chain them together: Annotation.body_equals('?').body_like('[?]')

I'm not sure how to combine them with OR.

Note that you can combine named scopes with OR if their argument is the same. E.g., I could do:

 Annotation.body_equals_or_body_like('?')

But this wouldn't help.

Note that I'm not attached to SearchLogic, but it would be great for a solution that doesn't require breaking its abstraction.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

梦初启 2024-08-13 03:03:53

“喜欢”结果不也包括“等于”结果吗?

您还可以在另一个命名范围的末尾使用一个命名范围来创建一个非常长的命名范围。来自 Searchlogic 文档(这种方式对我来说似乎有点冗长):

User.username_or_first_name_like("ben")
=> "username LIKE '%ben%' OR first_name like'%ben%'"

User.id_or_age_lt_or_username_or_first_name_begins_with(10)
=> "id < 10 OR age < 10 OR username LIKE 'ben%' OR first_name like'ben%'"

或者您可以使用联合来组合搜索结果数组,同时删除重复项:

@equal_results = Annotation.body_equals('?')
@like_results  = Annotation.body_like('[?]')
@results = @equal_results | @like_results

Wouldn't the 'like' results also include the 'equals' results?

You can also use a named scope on the end of another to make a really long named scope. From the Searchlogic Docs (this way seems a bit longwinded to me):

User.username_or_first_name_like("ben")
=> "username LIKE '%ben%' OR first_name like'%ben%'"

User.id_or_age_lt_or_username_or_first_name_begins_with(10)
=> "id < 10 OR age < 10 OR username LIKE 'ben%' OR first_name like'ben%'"

Or you can use a union to combine the search result arrays while removing the duplicates:

@equal_results = Annotation.body_equals('?')
@like_results  = Annotation.body_like('[?]')
@results = @equal_results | @like_results
葬心 2024-08-13 03:03:53

对于 Rails 2.x,您可以使用以下命名范围来模拟 OR:

    __or_fn = lambda do |*scopes|
    where = []
    joins = []
    includes = []

    # for some reason, flatten is actually executing the scope
    scopes = scopes[0] if scopes.size == 1
    scopes.each do |s|
      s = s.proxy_options
      begin
        where << merge_conditions(s[:conditions])
      rescue NoMethodError
        where << scopes[0].first.class.merge_conditions(s[:conditions])
      end
      joins << s[:joins] unless s[:joins].nil?
      includes << s[:include] unless s[:include].nil?
    end
    scoped = self
    scoped = scoped.includes(includes.uniq.flatten) unless includes.blank?
    scoped = scoped.joins(joins.uniq.flatten) unless joins.blank?
    scoped.where(where.join(" OR "))
  end
  named_scope :or, __or_fn

让我们通过上面的示例来使用此函数。

q1 = Annotation.body_equals('?')
q2 = Annotation.body_like('[?]')
Annotation.or(q1,q2)

上面的代码只执行一个查询。 q1q2 不保存查询结果,它们的类是 ActiveRecord::NamedScope::Scope

ornamed_scope 组合这些查询并使用 OR 连接条件。

您还可以嵌套 OR,就像在这个人为的示例中一样:

rabbits = Animal.rabbits
#<Animal id: 1 ...>
puppies = Animal.puppies
#<Animal id: 2 ...>
snakes = Animal.snakes
#<Animal id: 3 ...>
lizards = Animal.lizards
#<Animal id: 4 ...>

Animal.or(rabbits, puppies)
[#<Animal id: 1 ...>, #<Animal id: 2 ...>]
Animal.or(rabbits, puppies, snakes)
[#<Animal id: 1 ...>, #<Animal id: 2 ...>, #<Animal id: 3 ...>]

因为 or 返回一个 ActiveRecord::NamedScope::Scope 本身,我们可能会变得非常疯狂:

# now let's get crazy
or1 = Animal.or(rabbits, puppies)
or2 = Animal.or(snakes, lizards)
Animal.or(or1, or2)
[#<Animal id: 1 ...>, #<Animal id: 2 ...>, #<Animal id: 3 ...>, #<Animal id: 4...>]

我相信大多数尽管我还没有尝试过,但这些示例在 Rails 3 中使用 scope 可以正常工作。

一点无耻的自我推销 - fake_arel gem 中提供了此功能。

For Rails 2.x, you could use the following named scope to simulate OR:

    __or_fn = lambda do |*scopes|
    where = []
    joins = []
    includes = []

    # for some reason, flatten is actually executing the scope
    scopes = scopes[0] if scopes.size == 1
    scopes.each do |s|
      s = s.proxy_options
      begin
        where << merge_conditions(s[:conditions])
      rescue NoMethodError
        where << scopes[0].first.class.merge_conditions(s[:conditions])
      end
      joins << s[:joins] unless s[:joins].nil?
      includes << s[:include] unless s[:include].nil?
    end
    scoped = self
    scoped = scoped.includes(includes.uniq.flatten) unless includes.blank?
    scoped = scoped.joins(joins.uniq.flatten) unless joins.blank?
    scoped.where(where.join(" OR "))
  end
  named_scope :or, __or_fn

Let's use this function using your example above.

q1 = Annotation.body_equals('?')
q2 = Annotation.body_like('[?]')
Annotation.or(q1,q2)

The above code executes only one query. q1 and q2 do not hold the results of the query, rather, their class is ActiveRecord::NamedScope::Scope.

The or named_scope combines these queries and joins the conditions with an OR.

You could also nest ORs, like in this contrived example:

rabbits = Animal.rabbits
#<Animal id: 1 ...>
puppies = Animal.puppies
#<Animal id: 2 ...>
snakes = Animal.snakes
#<Animal id: 3 ...>
lizards = Animal.lizards
#<Animal id: 4 ...>

Animal.or(rabbits, puppies)
[#<Animal id: 1 ...>, #<Animal id: 2 ...>]
Animal.or(rabbits, puppies, snakes)
[#<Animal id: 1 ...>, #<Animal id: 2 ...>, #<Animal id: 3 ...>]

Because or returns a ActiveRecord::NamedScope::Scope itself, we can go really crazy:

# now let's get crazy
or1 = Animal.or(rabbits, puppies)
or2 = Animal.or(snakes, lizards)
Animal.or(or1, or2)
[#<Animal id: 1 ...>, #<Animal id: 2 ...>, #<Animal id: 3 ...>, #<Animal id: 4...>]

I believe that most of these examples would work fine using scopes in Rails 3, although I have not tried.

A Bit of shameless self-promotion - This functionality is available in the fake_arel gem.

慈悲佛祖 2024-08-13 03:03:53

我在这个问题上寻找“或”两个named_scopes的答案,所有的答案对我来说看起来都太复杂了。我进行了一些研究,找到了一个使用名为“or”的附加命名范围的解决方案,它可以解决问题。

按照给定的示例:

Annotation.body_equals('?')
Annotation.body_like('[?]')

两者都返回一个命名范围对象,该对象构造选择返回注释记录

现在我们定义另一个命名范围,期望两个命名范围作为参数,例如:

named_scope :or, lambda { |l, r| {
  :conditions => 
      "annotations.id IN (#{l.send(:construct_finder_sql,{:select => :id})}) or " + 
      "annotations.id IN (#{r.send(:construct_finder_sql,{:select => :id})})" 
}}

然后您可以使用:

Annotation.or(Annotation.body_equals('?'), Annotation.body_like('[?]'))

这将创建一个查询,例如:

select * from annotations 
where (annotations.id IN (select id from annotations where body='?') or
      (annotations.id IN (select id from annotations where body like '%?%')

这就是您所追求的

As 或也是一个命名范围,可以与其他命名范围链接,包括另一个或:

Annotation.or(Annotation.or(Annotation.body_equals('?'), 
                            Annotation.body_like('[?]')),
              Annotation.some_other)

I came over this question looking for the answer to "or" two named_scopes and all the answers looked too complex to me. I investigated a bit and found a solution using an additional named_scope called "or" which does the trick.

Following the given example:

Annotation.body_equals('?')
Annotation.body_like('[?]')

both return a named_scope object that construct selects returning annotation records

now we define another named scope expecting two named scopes as parameters like:

named_scope :or, lambda { |l, r| {
  :conditions => 
      "annotations.id IN (#{l.send(:construct_finder_sql,{:select => :id})}) or " + 
      "annotations.id IN (#{r.send(:construct_finder_sql,{:select => :id})})" 
}}

You can then use:

Annotation.or(Annotation.body_equals('?'), Annotation.body_like('[?]'))

This will create a query like:

select * from annotations 
where (annotations.id IN (select id from annotations where body='?') or
      (annotations.id IN (select id from annotations where body like '%?%')

Which is what you were after

As or is also a named_scope, it is possible to chain with other named_scopes including another or:

Annotation.or(Annotation.or(Annotation.body_equals('?'), 
                            Annotation.body_like('[?]')),
              Annotation.some_other)
小耗子 2024-08-13 03:03:53

大概是

Annotation.body_equals_or_body_like(['?', '[?]'])

Probably it's

Annotation.body_equals_or_body_like(['?', '[?]'])
梦在深巷 2024-08-13 03:03:52

我找不到任何简单的解决方案,但这个问题引起了我的兴趣,所以我推出了自己的解决方案:

class ActiveRecord::Base

  def self.or_scopes(*scopes)
    # Cleanup input
    scopes.map! do |scope|
      scope = scope.respond_to?(:to_a) ? scope.to_a : [*scope]
      scope.unshift(scope.shift.to_sym)
    end

    # Check for existence of scopes
    scopes.each{|scope| raise ArgumentError, "invalid scope: #{scope.first}" unless self.scopes.has_key?(scope.first) }

    conditions = scopes.map do |scope|
      scope = self.scopes[scope.first].call(self, *scope[1..-1])
      self.merge_conditions(scope.proxy_options[:conditions])
    end

    or_conditions = conditions.compact.join(" OR ")

    merged_scopes = scopes.inject(self){|merged, scope| merged.scopes[scope.first].call(self, *scope[1..-1]) }

    # We ignore other scope types but so does named_scopes
    find_options = merged_scopes.scope(:find).merge(:conditions => or_conditions)

    self.scoped(find_options)
  end

end

考虑以下设置:

class Person < ActiveRecord::Base
  named_scope :men,      :conditions => { :sex => 'M' }
  named_scope :women,    :conditions => { :sex => 'F' }
  named_scope :children, :conditions => "age < 18"
  named_scope :named, lambda{|name|
    { :conditions => { :name => name } }
  }
end

您可以使用一系列范围的名称来调用它:

Person.or_scopes(:women, :children)

这将返回一个如下的范围:

Person.or_scopes(:women, :children).proxy_options
# => {:conditions=>"(`people`.`sex` = 'F') OR (age < 18)"}

您可以当作用域需要参数时,也可以使用数组数组来调用它:

Person.or_scopes(:women, [:named, 'Sue']).proxy_options
# => {:conditions=>"(`people`.`sex` = 'F') OR (`people`.`name` = 'Sue')"}

在您的情况下 Horace,您可以使用以下内容:

Annotation.or_scopes([:body_equals, '?'], [:body_like, '[?']).all

I couldn't find any simple solutions, but this problem intrigued me, so I rolled my own solution:

class ActiveRecord::Base

  def self.or_scopes(*scopes)
    # Cleanup input
    scopes.map! do |scope|
      scope = scope.respond_to?(:to_a) ? scope.to_a : [*scope]
      scope.unshift(scope.shift.to_sym)
    end

    # Check for existence of scopes
    scopes.each{|scope| raise ArgumentError, "invalid scope: #{scope.first}" unless self.scopes.has_key?(scope.first) }

    conditions = scopes.map do |scope|
      scope = self.scopes[scope.first].call(self, *scope[1..-1])
      self.merge_conditions(scope.proxy_options[:conditions])
    end

    or_conditions = conditions.compact.join(" OR ")

    merged_scopes = scopes.inject(self){|merged, scope| merged.scopes[scope.first].call(self, *scope[1..-1]) }

    # We ignore other scope types but so does named_scopes
    find_options = merged_scopes.scope(:find).merge(:conditions => or_conditions)

    self.scoped(find_options)
  end

end

Consider the following setup:

class Person < ActiveRecord::Base
  named_scope :men,      :conditions => { :sex => 'M' }
  named_scope :women,    :conditions => { :sex => 'F' }
  named_scope :children, :conditions => "age < 18"
  named_scope :named, lambda{|name|
    { :conditions => { :name => name } }
  }
end

You call it with the names of a series of scopes as such:

Person.or_scopes(:women, :children)

This returns a scope like this:

Person.or_scopes(:women, :children).proxy_options
# => {:conditions=>"(`people`.`sex` = 'F') OR (age < 18)"}

You can also call it with an array of arrays when the scope requires parameters:

Person.or_scopes(:women, [:named, 'Sue']).proxy_options
# => {:conditions=>"(`people`.`sex` = 'F') OR (`people`.`name` = 'Sue')"}

In your case Horace, you could use the following:

Annotation.or_scopes([:body_equals, '?'], [:body_like, '[?']).all
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文