Tagging in Rails 3

What would be a good tagging solution in Rails 3?

+4
source share
3 answers

I looked at both solutions, but prefer https://github.com/mbleigh/acts-as-taggable-on over https://github.com/jviney/acts_as_taggable_on_steroids

Better documentation and seems to me more flexible.

+7
source

https://github.com/bradphelan/rocket_tag

is the new library I just created yesterday. It is implemented using the Ernie Miller gemstone where possible, so all the scary SQL needed to properly implement the tag library is pretty clean.

compare act_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 

in 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 

which is much cleaner, although I do not claim to have processed every function that act_as_taggable_on performed. There is always tomorrow :)

So, if you want the tag library you can dive in and add features to rocket_tag, perhaps this is what you want.

It also takes care of performance and avoids the N + 1 issue when loading related tags. It is worth a look, but at the moment it is very alpha, and I will add features as required by my project.

BTW. Thanks to action-like-taggable-on. I do not cry in the library. I borrowed the schema and ideas from it, but when I wanted to fix my own functions, I felt that the SQL style in the code is pretty hard to understand and after using https://github.com/ernie/squeel for my AR queries, I felt it was better to work with a clean list.

RocketTag also has a comprehensive rspec test suite https://github.com/bradphelan/rocket_tag/blob/master/spec/rocket_tag/taggable_spec.rb

+3
source
+1
source

Source: https://habr.com/ru/post/1341482/


All Articles