由于方法调用的缓存而导致的 SearchLogic 问题

发布于 2024-09-30 05:12:41 字数 2736 浏览 1 评论 0原文

我正在尝试使用 searchlogic gem 对几个表执行搜索发布 has_many 资产。如果资产不存在,我需要它执行左外连接而不是内连接。

根据我下面的内容,查询是使用所需的外连接生成的,并通过了前三个测试,但在最后一个测试中失败了。但是,如果我只运行最后一个测试,它就会通过。

失败的原因是 @search_logic_filter var 仅在第一个测试中设置,并用于所有其余测试。

以这种方式设置 @search_logic_filter 的原因是,它是对 method_missing 的唯一调用,该调用携带传递给动态 searchlogic 方法调用的参数 Post.title_or_body_or...like ("fun")

有没有更好的方法来设置过滤器参数?

test "find posts and assets by filter for user" do
  customer = users(:customer)

  create_post_for_user(customer, {:body => "Rails is fun", :tags => "rails ruby"})
  create_post_for_user(customer, {:body => "Fun is what Emacs is all about", :title => "emacs"})


  # File with post
  asset_post = create_post_for_user(customer, {:body => "Ruby is pretty fun too",
                                     :tags => "ruby"})
  asset_post.assets << Asset.new(:upload_file_name => "ruby_tips",
                                :upload_file_size => 100,
                                :upload_content_type => "text")
  asset_post.save

  # search post
  assert_equal 3, Post.find_for_user(customer.id, "fun").size
  assert_equal 2, Post.find_for_user(customer.id, "ruby").size
  assert_equal 1, Post.find_for_user(customer.id, "emacs").size

  # search asset
  puts "about to run last test"
  assert_equal 1, Post.find_for_user(customer.id, "ruby_tips").size
end

class Post < ActiveRecord::Base

  def self.find_for_user(user_id, filter, page=1)
    Post.
      user_id_equals(user_id).
      title_or_body_or_tags_or_assets_upload_file_name_like(filter).all
  end

  class << self
    def method_missing(name, *args, &block)
      if name.to_s =~ /\w+_or_\w+_like$/
        # ** only gets here once **
        @search_logic_filter = args.first
        super
      elsif name == :assets_upload_file_name_like
        # args is [] here which is the reason for the above setting of @search_logic_filter
        named_scope :assets_upload_file_name_like, lambda {
          {:joins => "left outer join assets on posts.id = assets.post_id",
            :conditions => "assets.upload_file_name like '%#{@search_logic_filter}%'"}
        }
        assets_upload_file_name_like
      else
        super
      end
    end
  end
end

** 更新 这是为最终测试运行的查询。请注意,upload_file_name 参数是“fun”,而不是“ruby_tips”。 “fun”参数对于 upload_file_name 列的所有测试都存在,但只对最后一个测试重要。

SELECT `posts`.* 
FROM `posts` 
  left outer join assets 
    on posts.id = assets.post_id 
WHERE (
  ((posts.title LIKE '%ruby_tips%') OR (posts.body LIKE '%ruby_tips%') OR (posts.tags LIKE '%ruby_tips%') OR (assets.upload_file_name like '%fun%')) 
  AND (posts.user_id = 20549131)
)

I am trying to use the searchlogic gem to perform searches over a couple tables Post has_many assets. I need it to perform left outer joins rather than inner joins in the event of a non-existant asset.

From what I have below the query is generated with the required outer joins, and passes the first three tests, but fails on the last. However, if I only run the last test it then passes.

The reason for the failing is that the @search_logic_filter var is only being set on the first test and is used for all of the remaining tests.

The reason for the setting of the @search_logic_filter in this way is that it is the only call to method_missing that carries the param passed to the dynamic searchlogic method call of Post.title_or_body_or...like("fun")

Is there a better way to set the filter param?

test "find posts and assets by filter for user" do
  customer = users(:customer)

  create_post_for_user(customer, {:body => "Rails is fun", :tags => "rails ruby"})
  create_post_for_user(customer, {:body => "Fun is what Emacs is all about", :title => "emacs"})


  # File with post
  asset_post = create_post_for_user(customer, {:body => "Ruby is pretty fun too",
                                     :tags => "ruby"})
  asset_post.assets << Asset.new(:upload_file_name => "ruby_tips",
                                :upload_file_size => 100,
                                :upload_content_type => "text")
  asset_post.save

  # search post
  assert_equal 3, Post.find_for_user(customer.id, "fun").size
  assert_equal 2, Post.find_for_user(customer.id, "ruby").size
  assert_equal 1, Post.find_for_user(customer.id, "emacs").size

  # search asset
  puts "about to run last test"
  assert_equal 1, Post.find_for_user(customer.id, "ruby_tips").size
end

class Post < ActiveRecord::Base

  def self.find_for_user(user_id, filter, page=1)
    Post.
      user_id_equals(user_id).
      title_or_body_or_tags_or_assets_upload_file_name_like(filter).all
  end

  class << self
    def method_missing(name, *args, &block)
      if name.to_s =~ /\w+_or_\w+_like$/
        # ** only gets here once **
        @search_logic_filter = args.first
        super
      elsif name == :assets_upload_file_name_like
        # args is [] here which is the reason for the above setting of @search_logic_filter
        named_scope :assets_upload_file_name_like, lambda {
          {:joins => "left outer join assets on posts.id = assets.post_id",
            :conditions => "assets.upload_file_name like '%#{@search_logic_filter}%'"}
        }
        assets_upload_file_name_like
      else
        super
      end
    end
  end
end

** update
This is the query that is run for the final test. Notice that the upload_file_name param is 'fun', not 'ruby_tips'. The 'fun' param exists for all the tests for the upload_file_name col, but it only matters for the last test.

SELECT `posts`.* 
FROM `posts` 
  left outer join assets 
    on posts.id = assets.post_id 
WHERE (
  ((posts.title LIKE '%ruby_tips%') OR (posts.body LIKE '%ruby_tips%') OR (posts.tags LIKE '%ruby_tips%') OR (assets.upload_file_name like '%fun%')) 
  AND (posts.user_id = 20549131)
)

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

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

发布评论

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

评论(1

暖阳 2024-10-07 05:12:41

您不应该以这种方式声明named_scope assets_upload_file_name_like。第一次调用时,assets_upload_file_name_like 命名范围是使用根据 @search_logic_filter 的值生成的 :conditions 的值来定义的。时间。您应该改为在 lambda 上设置参数。

也没有必要使用method_missing。只需在 Post 类中声明 named_scope 即可。另外,应该过滤查询以防止 SQL 注入攻击。

class Post < ActiveRecord::Base
  named_scope :assets_upload_file_name_like, lambda { |file_name| {
    :joins => "left outer join assets on posts.id = assets.post_id",
    # Prevent SQL injection.
    :conditions => ["assets.upload_file_name like ?", "%#{file_name}%"]
  }}
end

You should not declare the named_scope assets_upload_file_name_like that way. When it's called the first time, the assets_upload_file_name_like named scope is defined with the the value for :conditions generated according the value of @search_logic_filter at that time. You should set the parameter on the lambda instead.

There's also no need to use method_missing. Just declare the named_scope within the Post class. As a bonus, the query should be filtered to guard against SQL injection attacks.

class Post < ActiveRecord::Base
  named_scope :assets_upload_file_name_like, lambda { |file_name| {
    :joins => "left outer join assets on posts.id = assets.post_id",
    # Prevent SQL injection.
    :conditions => ["assets.upload_file_name like ?", "%#{file_name}%"]
  }}
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文