1

searchlogic gem を使用して、いくつかのテーブルで検索を実行しようとしていますPost has_many assets。アセットが存在しない場合に、内部結合ではなく左外部結合を実行するために必要です。

以下の内容から、必要な外部結合を使用してクエリが生成され、最初の 3 つのテストに合格しますが、最後のテストでは失敗します。ただし、最後のテストのみを実行すると、合格します。

失敗の理由は、変数が@search_logic_filter最初のテストでのみ設定され、残りのすべてのテストに使用されるためです。

をこのように設定する理由は@search_logic_filter、動的 searchlogic メソッド呼び出しに渡されるパラメーターを運ぶのは、method_missing への唯一の呼び出しであるためです。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

** update これは、最終テストのために実行されるクエリです。upload_file_name パラメーターが「ruby_tips」ではなく「fun」であることに注意してください。「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)
)
4

1 に答える 1

1

そのように宣言するべきではありませんnamed_scope assets_upload_file_name_like。初めて呼び出されたとき、assets_upload_file_name_like名前付きスコープは、その時点:conditionsの値に従って生成されたの値で定義され@search_logic_filterます。代わりに、パラメータをに設定する必要がありますlambda

を使用する必要もありませんmethod_missing。クラスnamed_scope内で宣言するだけです。Postボーナスとして、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
于 2010-11-03T04:12:45.217 に答える