2011-02-27 92 views

回答

3

https://github.com/bradphelan/rocket_tag

是我昨天刚刚创建了一个新的库。它使用Ernie Miller的squeel gem 实现,因此所有可怕的SQL都需要正确实现标签库 非常干净。

比较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_tags

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 

这是很多清洁剂,虽然我不声称已经处理每一个功能的acts_as_taggable_on已经实施。总是有明天:)

所以,如果你想要一个标签库,你可以潜入并添加功能到rocket_tag可能是你想要的。

它也注意性能,避免加载相关标签时出现N + 1问题。这是值得一看,但是目前我还需要alpha,因为我的项目需要它们。

顺便说一句。感谢作为标签的行为。我不会燃烧图书馆。我借用了架构和想法,但是当我想修复自己的功能时,我觉得代码中的SQL风格很难理解,并且在使用https://github.com/ernie/squeel来满足我的AR查询需求后,我觉得我可以更好地使用干净的平板。

RocketTag还具备全面的RSpec的测试套件https://github.com/bradphelan/rocket_tag/blob/master/spec/rocket_tag/taggable_spec.rb

+0

警告:仅限Ruby 1.9.2 + – 2012-04-16 18:34:58