2011-02-27 6 views

답변

3

https://github.com/bradphelan/rocket_tag

내가 어제 만든 새로운 라이브러리입니다. 가능한 한 태그 라이브러리를 구현하는 데 필요한 무서운 SQL이 모두 인 경우 Ernie Miller의 축복형 젬 을 사용하여 구현되었습니다. 은 매우 깨끗합니다. 나는 모든 acts_as_taggable_on가 구현 한 기능을 처리했다고 주장하지 않습니다하지만

는 많은 청소기입니다

def with_tag_context context 
    if context 
     where{taggings.context == my{context} } 
    else 
     where{} 
    end 
    end 

    def tagged_with tags_list, options = {} 

    on = options.delete :on 
    all = options.delete :all 

    q = if all 
      joins{tags}.where{ 
      id.in( 
       my{self}. 
        select{id}. 
        joins{tags}. 
        where{tags.name.in(my{tags_list})}. 
        group{~id}. 
        having{count(~id)==my{tags_list.length}}. 
        with_tag_context(my{on}) 
      ) 
      } 
    else 
     joins{tags}.where{tags.name.in(my{tags_list})}.with_tag_context(on) 
    end 

    q.select{"distinct #{my{table_name}}.*"} 

    end 

을 rocket_tags하는 acts_as_taggable_ons

def tagged_with(tags, options = {}) 
    tag_list = ActsAsTaggableOn::TagList.from(tags) 
    empty_result = scoped(:conditions => "1 = 0") 

    return empty_result if tag_list.empty? 

    joins = [] 
    conditions = [] 

    context = options.delete(:on) 
    alias_base_name = undecorated_table_name.gsub('.','_') 

    if options.delete(:exclude) 
     tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ") 
     conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})" 

    elsif options.delete(:any) 
     # get tags, drop out if nothing returned (we need at least one) 
     tags = ActsAsTaggableOn::Tag.named_any(tag_list) 
     return scoped(:conditions => "1 = 0") unless tags.length > 0 

     # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123 
     # avoid ambiguous column name 
     taggings_context = context ? "_#{context}" : '' 

     #TODO: fix alias to be smaller 
     taggings_alias = "#{alias_base_name}#{taggings_context}_taggings_#{tags.map(&:safe_name).join('_')}_#{rand(1024)}" 

     tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + 
         " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + 
         " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" 
     tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context 

     # don't need to sanitize sql, map all ids and join with OR logic 
     conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{t.id}" }.join(" OR ") 
     select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one? 

     joins << tagging_join 

    else 
     tags = ActsAsTaggableOn::Tag.named_any(tag_list) 
     return empty_result unless tags.length == tag_list.length 

     tags.each do |tag| 
     prefix = "#{tag.safe_name}_#{rand(1024)}" 

     taggings_alias = "#{alias_base_name}_taggings_#{prefix}" 

     tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + 
         " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + 
         " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" + 
         " AND #{taggings_alias}.tag_id = #{tag.id}" 
     tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context 

     joins << tagging_join 
     end 
    end 

    taggings_alias, tags_alias = "#{alias_base_name}_taggings_group", "#{alias_base_name}_tags_group" 

    if options.delete(:match_all) 
     joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + 
       " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + 
       " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" 


     group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" 
     group = "#{group_columns} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}" 
    end 

    scoped(:select  => select_clause, 
      :joins  => joins.join(" "), 
      :group  => group, 
      :conditions => conditions.join(" AND "), 
      :order  => options[:order], 
      :readonly => false) 
    end 

을 비교합니다. 항상 내일은 있습니다 :)

태그 라이브러리를 원한다면 rocket_tag에 기능을 추가하여 원하는 것을 추가 할 수 있습니다.

또한 성능에주의하고 관련 태그를로드 할 때 N + 1 문제를 방지합니다. 볼만한 가치가 있지만 지금은 알파이며 프로젝트에서 요구하는대로 기능을 추가하겠습니다.

BTW. 태그와 같은 행위에 감사드립니다. 나는 도서관에 불을 붙이는 방법이 아니다. 나는 스키마와 아이디어를 빌려 왔지만 자신 만의 기능을 고치고 자 할 때 코드의 SQL 스타일이 이해하기가 어려웠고을 AR 쿼리에 사용하면 깨끗한 슬레이트로 더 잘 작업 할 수 있다고 생각했습니다. . https://github.com/mbleigh/acts-as-taggable-on :

RocketTag도 https://github.com/bradphelan/rocket_tag/blob/master/spec/rocket_tag/taggable_spec.rb

+0

경고 : Ruby 1.9.2 이상 –