Module: Tagtical::Taggable::Core::ClassMethods
- Defined in:
- lib/tagtical/taggable/core.rb
Instance Method Summary collapse
- #acts_as_taggable(*args) ⇒ Object
-
#define_tag_scope(tag_type) ⇒ Object
If the tag_type is base? (type==“tag”), then we add additional functionality to the AR has_many :tags.
- #find_tag_type!(input, options = {}) ⇒ Object
-
#grouped_column_names_for(object) ⇒ Object
all column names are necessary for PostgreSQL group clause.
- #initialize_tagtical_core ⇒ Object
- #is_taggable? ⇒ Boolean
-
#tagged_with(tags, options = {}) ⇒ Object
Return a scope of objects that are tagged with the specified tags.
Instance Method Details
#acts_as_taggable(*args) ⇒ Object
54 55 56 57 |
# File 'lib/tagtical/taggable/core.rb', line 54 def acts_as_taggable(*args) super(*args) initialize_tagtical_core end |
#define_tag_scope(tag_type) ⇒ Object
If the tag_type is base? (type==“tag”), then we add additional functionality to the AR has_many :tags.
taggable_model.tags(:scope => :children)
taggable_model.tags <-- still works like normal has_many
taggable_model.tags(true, :scope => :current) <-- reloads the tags association and appends scope for only current type.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/tagtical/taggable/core.rb', line 65 def define_tag_scope(tag_type) if tag_type.has_many_name==:tags define_method("tags_with_finder_type_options") do |*args| bool = args.shift if [true, false].include?(args.first) = (bool) args.empty? ? : (tag_type, *args) end alias_method_chain :tags, :finder_type_options else # handle the Tagtical::Tag subclasses define_method(tag_type.scope_name) do |*args| if args.empty? instance_variable_get(tag_type.scope_ivar) || instance_variable_set(tag_type.scope_ivar, .scoped.merge(tag_type.scoping).tap do |scope| if (loaded_parent_scope = tag_type.(:parents).map { |t| tag_scope(t) }.detect(&:loaded?)) scope.instance_variable_set(:@loaded, true) scope.instance_variable_set(:@records, loaded_parent_scope.select { |t| t.class <= tag_type.klass }) end end) else (tag_type, *args) end end end end |
#find_tag_type!(input, options = {}) ⇒ Object
95 96 97 |
# File 'lib/tagtical/taggable/core.rb', line 95 def find_tag_type!(input, ={}) (@tag_type ||= {})[input] ||= tag_types.find { |t| t.match?(input) } || raise("Cannot find tag type:'#{input}' in #{tag_types.inspect}") end |
#grouped_column_names_for(object) ⇒ Object
all column names are necessary for PostgreSQL group clause
91 92 93 |
# File 'lib/tagtical/taggable/core.rb', line 91 def grouped_column_names_for(object) object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ") end |
#initialize_tagtical_core ⇒ Object
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/tagtical/taggable/core.rb', line 15 def initialize_tagtical_core has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "Tagtical::Tagging" has_many :tags, :through => :taggings, :source => :tag, :class_name => "Tagtical::Tag", :select => "#{Tagtical::Tag.table_name}.*, #{Tagtical::Tagging.table_name}.relevance, #{Tagtical::Tagging.table_name}.tagger_id" # include the relevance on the tags tag_types.each do |tag_type| # has_many :tags gets created here # Aryk: Instead of defined multiple associations for the different types of tags, I decided # to define the main associations (tags and taggings) and use AR scope's to build off of them. # This keeps your reflections cleaner. # In the case of the base tag type, it will just use the :tags association defined above. Tagtical::Tag.define_scope_for_type(tag_type) define_tag_scope(tag_type) class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.with_#{tag_type.pluralize}(*tags) options = tags.extract_options! tagged_with(tags.flatten, options.merge(:on => :#{tag_type})) end def #{tag_type}_list(*args) tag_list_on('#{tag_type}', *args) end def #{tag_type}_list=(new_tags, *args) set_tag_list_on('#{tag_type}', new_tags, *args) end alias set_#{tag_type}_list #{tag_type}_list= def all_#{tag_type.pluralize}_list(*args) all_tags_list_on('#{tag_type}', *args) end RUBY end end |
#is_taggable? ⇒ Boolean
193 194 195 |
# File 'lib/tagtical/taggable/core.rb', line 193 def is_taggable? true end |
#tagged_with(tags, options = {}) ⇒ Object
Return a scope of objects that are tagged with the specified tags.
Example:
User.tagged_with("awesome", "cool") # Users that are tagged with awesome and cool
User.tagged_with("awesome", "cool", :exclude => true) # Users that are not tagged with awesome or cool
User.tagged_with("awesome", "cool", :any => true) # Users that are tagged with awesome or cool
User.tagged_with("awesome", "cool", :match_all => true) # Users that are tagged with just awesome and cool
User.tagged_with("awesome", "cool", :on => :skills) # Users that are tagged with just awesome and cool on skills
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/tagtical/taggable/core.rb', line 114 def tagged_with(, = {}) tag_list = Tagtical::TagList.from() return scoped(:conditions => "1 = 0") if tag_list.empty? && ![:exclude] joins = [] conditions = [] tag_type = find_tag_type!(.delete(:on) || Tagtical::Tag::Type::BASE) = .extract!(:scope) tag_table, tagging_table = Tagtical::Tag.table_name, Tagtical::Tagging.table_name if .delete(:exclude) conditions << "#{table_name}.#{primary_key} NOT IN (" + "SELECT #{tagging_table}.taggable_id " + "FROM #{tagging_table} " + "JOIN #{tag_table} ON #{tagging_table}.tag_id = #{tag_table}.id AND #{tag_list.to_sql_conditions(:operator => "LIKE")} " + "WHERE #{tagging_table}.taggable_type = #{quote_value(base_class.name)})" elsif .delete(:any) conditions << tag_list.to_sql_conditions(:operator => "LIKE") tagging_join = " JOIN #{tagging_table}" + " ON #{tagging_table}.taggable_id = #{table_name}.#{primary_key}" + " AND #{tagging_table}.taggable_type = #{quote_value(base_class.name)}" + " JOIN #{tag_table}" + " ON #{tagging_table}.tag_id = #{tag_table}.id" if (finder_condition = tag_type.finder_type_condition(.merge(:sql => true))).present? conditions << finder_condition end select_clause = "DISTINCT #{table_name}.*" if tag_type.klass.descends_from_active_record? || !tag_types.one? joins << tagging_join else = tag_type.scoping().where_any_like(tag_list).group_by(&:value) return scoped(:conditions => "1 = 0") unless .length == tag_list.length # allow for chaining # Create only one join per tag value. .each do |value, | .each do |tag| safe_tag = value.gsub(/[^a-zA-Z0-9]/, '') prefix = "#{safe_tag}_#{rand(1024)}" taggings_alias = "#{undecorated_table_name}_taggings_#{prefix}" tagging_join = "JOIN #{tagging_table} #{taggings_alias}" + " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" + " AND #{sanitize_sql("#{taggings_alias}.tag_id" => .map(&:id))}" joins << tagging_join end end end taggings_alias, = "#{undecorated_table_name}_taggings_group", "#{undecorated_table_name}_tags_group" if .delete(:match_all) joins << "LEFT OUTER JOIN #{tagging_table} #{taggings_alias}" + " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" group_columns = Tagtical::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" group = "#{group_columns} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tag_list.size}" end scoped(:select => select_clause, :joins => joins.join(" "), :group => group, :conditions => conditions.join(" AND "), :order => [:order], :readonly => false) end |