2010-11-03 5 views
1

J'essaie d'utiliser la gemme searchlogic pour effectuer des recherches sur plusieurs tables Post has_many assets. J'en ai besoin pour effectuer des jointures externes à gauche plutôt que des jointures internes dans le cas d'un actif inexistant. De ce que j'ai ci-dessous la requête est générée avec les jointures externes requises, et passe les trois premiers tests, mais échoue sur le dernier. Cependant, si je ne fais que le dernier test, il passe ensuite.Problèmes de SearchLogic dus à la mise en cache des appels de méthode

La raison de l'échec est que la var @search_logic_filter est uniquement définie lors du premier test et est utilisée pour tous les tests restants.

La raison pour le réglage de la @search_logic_filter de cette manière est qu'il est le seul appel à method_missing qui porte le param transmis à l'appel de méthode searchlogic dynamique de Post.title_or_body_or...like("fun")

est-il une meilleure façon de régler le filtre 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 

** Mise à jour C'est la requête qui est exécutée pour le test final. Notez que le paramètre upload_file_name est 'fun', pas 'ruby_tips'. Le paramètre 'fun' existe pour tous les tests de la colonne upload_file_name, mais cela n'a d'importance que pour le dernier 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) 
) 

Répondre

1

Vous ne devez pas déclarer le named_scope assets_upload_file_name_like de cette façon. Lorsqu'elle est appelée la première fois, la portée nommée assets_upload_file_name_like est définie avec la valeur :conditions générée selon la valeur de @search_logic_filter à ce moment-là. Vous devez définir le paramètre sur lambda à la place. Il est également inutile d'utiliser method_missing. Déclarez simplement le named_scope dans la classe Post. En prime, la requête doit être filtrée pour se prémunir contre les attaques par injection 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 
+0

Merci pour l'aide :) – chris

Questions connexes