yeshoua_crm 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/Rakefile +11 -0
 - data/lib/yeshoua_crm.rb +87 -0
 - data/lib/yeshoua_crm/acts_as_draftable/draft.rb +40 -0
 - data/lib/yeshoua_crm/acts_as_draftable/rcrm_acts_as_draftable.rb +154 -0
 - data/lib/yeshoua_crm/acts_as_list/list.rb +282 -0
 - data/lib/yeshoua_crm/acts_as_taggable/rcrm_acts_as_taggable.rb +350 -0
 - data/lib/yeshoua_crm/acts_as_taggable/tag.rb +81 -0
 - data/lib/yeshoua_crm/acts_as_taggable/tag_list.rb +111 -0
 - data/lib/yeshoua_crm/acts_as_taggable/tagging.rb +16 -0
 - data/lib/yeshoua_crm/acts_as_viewed/rcrm_acts_as_viewed.rb +274 -0
 - data/lib/yeshoua_crm/acts_as_votable/rcrm_acts_as_votable.rb +80 -0
 - data/lib/yeshoua_crm/acts_as_votable/rcrm_acts_as_voter.rb +20 -0
 - data/lib/yeshoua_crm/acts_as_votable/votable.rb +323 -0
 - data/lib/yeshoua_crm/acts_as_votable/vote.rb +28 -0
 - data/lib/yeshoua_crm/acts_as_votable/voter.rb +131 -0
 - data/lib/yeshoua_crm/assets_manager.rb +43 -0
 - data/lib/yeshoua_crm/currency.rb +439 -0
 - data/lib/yeshoua_crm/currency/formatting.rb +224 -0
 - data/lib/yeshoua_crm/currency/heuristics.rb +151 -0
 - data/lib/yeshoua_crm/currency/loader.rb +24 -0
 - data/lib/yeshoua_crm/helpers/external_assets_helper.rb +17 -0
 - data/lib/yeshoua_crm/helpers/form_tag_helper.rb +123 -0
 - data/lib/yeshoua_crm/helpers/tags_helper.rb +13 -0
 - data/lib/yeshoua_crm/helpers/vote_helper.rb +35 -0
 - data/lib/yeshoua_crm/liquid/drops/cells_drop.rb +86 -0
 - data/lib/yeshoua_crm/liquid/drops/issues_drop.rb +66 -0
 - data/lib/yeshoua_crm/liquid/drops/news_drop.rb +54 -0
 - data/lib/yeshoua_crm/liquid/drops/users_drop.rb +72 -0
 - data/lib/yeshoua_crm/liquid/filters/arrays.rb +177 -0
 - data/lib/yeshoua_crm/liquid/filters/base.rb +208 -0
 - data/lib/yeshoua_crm/money_helper.rb +65 -0
 - data/lib/yeshoua_crm/version.rb +3 -0
 - data/test/acts_as_draftable/draft_test.rb +29 -0
 - data/test/acts_as_draftable/rcrm_acts_as_draftable_test.rb +185 -0
 - data/test/acts_as_taggable/rcrm_acts_as_taggable_test.rb +345 -0
 - data/test/acts_as_taggable/tag_list_test.rb +34 -0
 - data/test/acts_as_taggable/tag_test.rb +72 -0
 - data/test/acts_as_taggable/tagging_test.rb +15 -0
 - data/test/acts_as_viewed/rcrm_acts_as_viewed_test.rb +47 -0
 - data/test/acts_as_votable/rcrm_acts_as_votable_test.rb +19 -0
 - data/test/acts_as_votable/rcrm_acts_as_voter_test.rb +14 -0
 - data/test/acts_as_votable/votable_test.rb +507 -0
 - data/test/acts_as_votable/voter_test.rb +296 -0
 - data/test/currency_test.rb +292 -0
 - data/test/liquid/drops/issues_drop_test.rb +34 -0
 - data/test/liquid/drops/news_drop_test.rb +38 -0
 - data/test/liquid/drops/projects_drop_test.rb +44 -0
 - data/test/liquid/drops/uses_drop_test.rb +36 -0
 - data/test/liquid/filters/arrays_filter_test.rb +24 -0
 - data/test/liquid/filters/base_filter_test.rb +63 -0
 - data/test/liquid/liquid_helper.rb +32 -0
 - data/test/models/issue.rb +14 -0
 - data/test/models/news.rb +3 -0
 - data/test/models/project.rb +8 -0
 - data/test/models/user.rb +11 -0
 - data/test/models/vote_classes.rb +33 -0
 - data/test/money_helper_test.rb +12 -0
 - data/test/schema.rb +121 -0
 - data/test/tags_helper_test.rb +29 -0
 - data/test/test_helper.rb +66 -0
 - data/test/vote_helper_test.rb +28 -0
 - data/yeshoua_crm.gemspec +28 -0
 - metadata +206 -0
 
| 
         @@ -0,0 +1,350 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module YeshouaCrm
         
     | 
| 
      
 4 
     | 
    
         
            +
              module ActsAsTaggable #:nodoc:
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Taggable #:nodoc:
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def self.included(base)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    base.extend(ClassMethods)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  module ClassMethods
         
     | 
| 
      
 11 
     | 
    
         
            +
                    def taggable?
         
     | 
| 
      
 12 
     | 
    
         
            +
                      false
         
     | 
| 
      
 13 
     | 
    
         
            +
                    end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    def rcrm_acts_as_taggable
         
     | 
| 
      
 16 
     | 
    
         
            +
                      has_many :taggings, :as => :taggable, :dependent => :destroy, :class_name => '::YeshouaCrm::ActsAsTaggable::Tagging'
         
     | 
| 
      
 17 
     | 
    
         
            +
                      has_many :tags, :through => :taggings, :class_name => '::YeshouaCrm::ActsAsTaggable::Tag'
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                      before_save :save_cached_tag_list
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      after_create :save_tags
         
     | 
| 
      
 22 
     | 
    
         
            +
                      after_update :save_tags
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                      include YeshouaCrm::ActsAsTaggable::Taggable::InstanceMethods
         
     | 
| 
      
 25 
     | 
    
         
            +
                      extend YeshouaCrm::ActsAsTaggable::Taggable::SingletonMethods
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      alias_method :reload_without_tag_list, :reload
         
     | 
| 
      
 28 
     | 
    
         
            +
                      alias_method :reload, :reload_with_tag_list
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                      class_eval do
         
     | 
| 
      
 31 
     | 
    
         
            +
                        def self.taggable?
         
     | 
| 
      
 32 
     | 
    
         
            +
                          true
         
     | 
| 
      
 33 
     | 
    
         
            +
                        end
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    def cached_tag_list_column_name
         
     | 
| 
      
 38 
     | 
    
         
            +
                      'cached_tag_list'
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    def set_cached_tag_list_column_name(value = nil, &block)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      define_attr_method :cached_tag_list_column_name, value, &block
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    # Create the taggable tables
         
     | 
| 
      
 46 
     | 
    
         
            +
                    # === Options hash:
         
     | 
| 
      
 47 
     | 
    
         
            +
                    # * <tt>:table_name</tt> - use a table name other than viewings
         
     | 
| 
      
 48 
     | 
    
         
            +
                    # To be used during migration, but can also be used in other places
         
     | 
| 
      
 49 
     | 
    
         
            +
                    def create_taggable_table(options = {})
         
     | 
| 
      
 50 
     | 
    
         
            +
                      tag_name_table = options[:tags] || :tags
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                      if !self.connection.table_exists?(tag_name_table)
         
     | 
| 
      
 53 
     | 
    
         
            +
                        self.connection.create_table(tag_name_table) do |t|
         
     | 
| 
      
 54 
     | 
    
         
            +
                          t.column :name, :string
         
     | 
| 
      
 55 
     | 
    
         
            +
                        end
         
     | 
| 
      
 56 
     | 
    
         
            +
                      end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                      taggings_name_table = options[:taggings] || :taggings
         
     | 
| 
      
 59 
     | 
    
         
            +
                      if !self.connection.table_exists?(taggings_name_table)
         
     | 
| 
      
 60 
     | 
    
         
            +
                        self.connection.create_table(taggings_name_table) do |t|
         
     | 
| 
      
 61 
     | 
    
         
            +
                          t.column :tag_id, :integer
         
     | 
| 
      
 62 
     | 
    
         
            +
                          t.column :taggable_id, :integer
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                          # You should make sure that the column created is
         
     | 
| 
      
 65 
     | 
    
         
            +
                          # long enough to store the required class names.
         
     | 
| 
      
 66 
     | 
    
         
            +
                          t.column :taggable_type, :string
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                          t.column :created_at, :datetime
         
     | 
| 
      
 69 
     | 
    
         
            +
                        end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                        self.connection.add_index :taggings, :tag_id
         
     | 
| 
      
 72 
     | 
    
         
            +
                        self.connection.add_index :taggings, [:taggable_id, :taggable_type]
         
     | 
| 
      
 73 
     | 
    
         
            +
                      end
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    def drop_taggable_table(options = {})
         
     | 
| 
      
 77 
     | 
    
         
            +
                      tag_name_table = options[:tags] || :tags
         
     | 
| 
      
 78 
     | 
    
         
            +
                      if self.connection.table_exists?(tag_name_table)
         
     | 
| 
      
 79 
     | 
    
         
            +
                        self.connection.drop_table tag_name_table
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                      taggings_name_table = options[:taggings] || :taggings
         
     | 
| 
      
 83 
     | 
    
         
            +
                      if self.connection.table_exists?(taggings_name_table)
         
     | 
| 
      
 84 
     | 
    
         
            +
                        self.connection.drop_table taggings_name_table
         
     | 
| 
      
 85 
     | 
    
         
            +
                      end
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  module SingletonMethods
         
     | 
| 
      
 90 
     | 
    
         
            +
                    #Return all avalible tags for a cell or global
         
     | 
| 
      
 91 
     | 
    
         
            +
                    #Example: Question.available_tags(:cell => @cell_id )
         
     | 
| 
      
 92 
     | 
    
         
            +
                    def available_tags(options = {})
         
     | 
| 
      
 93 
     | 
    
         
            +
                      cell = options[:cell]
         
     | 
| 
      
 94 
     | 
    
         
            +
                      limit = options[:limit].to_i.zero? ? 30 : options[:limit].to_i
         
     | 
| 
      
 95 
     | 
    
         
            +
                      scope = Tag.where({})
         
     | 
| 
      
 96 
     | 
    
         
            +
                      class_name = quote_string_value(base_class.name)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      join = []
         
     | 
| 
      
 98 
     | 
    
         
            +
                      join << "JOIN #{Tagging.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id "
         
     | 
| 
      
 99 
     | 
    
         
            +
                      join << "JOIN #{table_name} ON #{table_name}.id = #{Tagging.table_name}.taggable_id
         
     | 
| 
      
 100 
     | 
    
         
            +
                        AND #{Tagging.table_name}.taggable_type = #{class_name} "
         
     | 
| 
      
 101 
     | 
    
         
            +
                      if attribute_names.include?('cell_id') && cell
         
     | 
| 
      
 102 
     | 
    
         
            +
                        join << "JOIN #{Cell.table_name} ON #{Cell.table_name}.id = #{table_name}.cell_id"
         
     | 
| 
      
 103 
     | 
    
         
            +
                        scope = scope.where("#{table_name}.cell_id = ?", cell.id)
         
     | 
| 
      
 104 
     | 
    
         
            +
                      end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                      if options[:name_like]
         
     | 
| 
      
 107 
     | 
    
         
            +
                        scope = scope.where("LOWER(#{Tag.table_name}.name) LIKE LOWER(?)", "%#{options[:name_like]}%")
         
     | 
| 
      
 108 
     | 
    
         
            +
                      end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                      group_fields = ''
         
     | 
| 
      
 111 
     | 
    
         
            +
                      group_fields << ", #{Tag.table_name}.created_at" if Tag.respond_to?(:created_at)
         
     | 
| 
      
 112 
     | 
    
         
            +
                      group_fields << ", #{Tag.table_name}.updated_at" if Tag.respond_to?(:updated_at)
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                      scope = scope.joins(join.join(' '))
         
     | 
| 
      
 115 
     | 
    
         
            +
                      scope = scope.select("#{Tag.table_name}.*, COUNT(DISTINCT #{Tagging.table_name}.taggable_id) AS count")
         
     | 
| 
      
 116 
     | 
    
         
            +
                      scope = scope.group("#{Tag.table_name}.id, #{Tag.table_name}.name #{group_fields}")
         
     | 
| 
      
 117 
     | 
    
         
            +
                      scope = scope.having('COUNT(*) > 0')
         
     | 
| 
      
 118 
     | 
    
         
            +
                      scope = scope.order("#{Tag.table_name}.name")
         
     | 
| 
      
 119 
     | 
    
         
            +
                      scope = scope.limit(limit)
         
     | 
| 
      
 120 
     | 
    
         
            +
                      scope
         
     | 
| 
      
 121 
     | 
    
         
            +
                    end
         
     | 
| 
      
 122 
     | 
    
         
            +
                    # Returns an array of related tags.
         
     | 
| 
      
 123 
     | 
    
         
            +
                    # Related tags are all the other tags that are found on the models tagged with the provided tags.
         
     | 
| 
      
 124 
     | 
    
         
            +
                    #
         
     | 
| 
      
 125 
     | 
    
         
            +
                    # Pass either a tag, string, or an array of strings or tags.
         
     | 
| 
      
 126 
     | 
    
         
            +
                    #
         
     | 
| 
      
 127 
     | 
    
         
            +
                    # Options:
         
     | 
| 
      
 128 
     | 
    
         
            +
                    #   :order - SQL Order how to order the tags. Defaults to "count DESC, tags.name".
         
     | 
| 
      
 129 
     | 
    
         
            +
                    def find_related_tags(tags, options = {})
         
     | 
| 
      
 130 
     | 
    
         
            +
                      tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                      related_models = find_tagged_with(tags)
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                      return [] if related_models.blank?
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                      related_ids = related_models.map{|c| c.id }.join(",")
         
     | 
| 
      
 137 
     | 
    
         
            +
                      Tag.select(
         
     | 
| 
      
 138 
     | 
    
         
            +
                        "#{Tag.table_name}.*, COUNT(#{Tag.table_name}.id) AS count").joins(
         
     | 
| 
      
 139 
     | 
    
         
            +
                        "JOIN #{Tagging.table_name} ON #{Tagging.table_name}.taggable_type = '#{base_class.name}'
         
     | 
| 
      
 140 
     | 
    
         
            +
                          AND  #{Tagging.table_name}.taggable_id IN (#{related_ids})
         
     | 
| 
      
 141 
     | 
    
         
            +
                          AND  #{Tagging.table_name}.tag_id = #{Tag.table_name}.id").order(
         
     | 
| 
      
 142 
     | 
    
         
            +
                        options[:order] || "count DESC, #{Tag.table_name}.name").group(
         
     | 
| 
      
 143 
     | 
    
         
            +
                        "#{Tag.table_name}.id, #{Tag.table_name}.name HAVING #{Tag.table_name}.name NOT IN (#{tags.map { |n| quote_string_value(n) }.join(",")})")
         
     | 
| 
      
 144 
     | 
    
         
            +
                    end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                    # Pass either a tag, string, or an array of strings or tags.
         
     | 
| 
      
 147 
     | 
    
         
            +
                    #
         
     | 
| 
      
 148 
     | 
    
         
            +
                    # Options:
         
     | 
| 
      
 149 
     | 
    
         
            +
                    #   :exclude - Find models that are not tagged with the given tags
         
     | 
| 
      
 150 
     | 
    
         
            +
                    #   :match_all - Find models that match all of the given tags, not just one
         
     | 
| 
      
 151 
     | 
    
         
            +
                    #   :conditions - A piece of SQL conditions to add to the query
         
     | 
| 
      
 152 
     | 
    
         
            +
                    def find_tagged_with(*args)
         
     | 
| 
      
 153 
     | 
    
         
            +
                      options = find_options_for_find_tagged_with(*args)
         
     | 
| 
      
 154 
     | 
    
         
            +
                      options.blank? ? [] : select(options[:select]).where(options[:conditions]).joins(options[:joins]).order(options[:order]).to_a
         
     | 
| 
      
 155 
     | 
    
         
            +
                    end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                    alias_method :tagged_with, :find_tagged_with
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                    def find_options_for_find_tagged_with(tags, options = {})
         
     | 
| 
      
 160 
     | 
    
         
            +
                      tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)
         
     | 
| 
      
 161 
     | 
    
         
            +
                      options = options.dup
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                      return {} if tags.empty?
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                      conditions = []
         
     | 
| 
      
 166 
     | 
    
         
            +
                      conditions << sanitize_sql(options.delete(:conditions)) if options[:conditions]
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                      taggings_alias, tags_alias = "#{table_name}_taggings", "#{table_name}_tags"
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                      joins = [
         
     | 
| 
      
 171 
     | 
    
         
            +
                        "INNER JOIN #{Tagging.table_name} #{taggings_alias} ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key} AND #{taggings_alias}.taggable_type = #{quote_string_value(base_class.name)}",
         
     | 
| 
      
 172 
     | 
    
         
            +
                        "INNER JOIN #{Tag.table_name} #{tags_alias} ON #{tags_alias}.id = #{taggings_alias}.tag_id"
         
     | 
| 
      
 173 
     | 
    
         
            +
                      ]
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                      if options.delete(:exclude)
         
     | 
| 
      
 176 
     | 
    
         
            +
                        conditions << <<-END
         
     | 
| 
      
 177 
     | 
    
         
            +
                          #{table_name}.id NOT IN
         
     | 
| 
      
 178 
     | 
    
         
            +
                            (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name}
         
     | 
| 
      
 179 
     | 
    
         
            +
                             INNER JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id
         
     | 
| 
      
 180 
     | 
    
         
            +
                             WHERE #{tags_condition(tags)} AND #{Tagging.table_name}.taggable_type = #{quote_string_value(base_class.name)})
         
     | 
| 
      
 181 
     | 
    
         
            +
                        END
         
     | 
| 
      
 182 
     | 
    
         
            +
                      else
         
     | 
| 
      
 183 
     | 
    
         
            +
                        if options.delete(:match_all)
         
     | 
| 
      
 184 
     | 
    
         
            +
                          joins << joins_for_match_all_tags(tags)
         
     | 
| 
      
 185 
     | 
    
         
            +
                        else
         
     | 
| 
      
 186 
     | 
    
         
            +
                          conditions << tags_condition(tags, tags_alias)
         
     | 
| 
      
 187 
     | 
    
         
            +
                        end
         
     | 
| 
      
 188 
     | 
    
         
            +
                      end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                      { :select => "DISTINCT #{table_name}.* ",
         
     | 
| 
      
 191 
     | 
    
         
            +
                        :joins => joins.join(" "),
         
     | 
| 
      
 192 
     | 
    
         
            +
                        :conditions => conditions.join(" AND ")
         
     | 
| 
      
 193 
     | 
    
         
            +
                      }.reverse_merge!(options)
         
     | 
| 
      
 194 
     | 
    
         
            +
                    end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
                    def joins_for_match_all_tags(tags)
         
     | 
| 
      
 197 
     | 
    
         
            +
                      joins = []
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                      tags.each_with_index do |tag, index|
         
     | 
| 
      
 200 
     | 
    
         
            +
                        taggings_alias, tags_alias = "taggings_#{index}", "tags_#{index}"
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                        join = <<-END
         
     | 
| 
      
 203 
     | 
    
         
            +
                          INNER JOIN #{Tagging.table_name} #{taggings_alias} ON
         
     | 
| 
      
 204 
     | 
    
         
            +
                            #{taggings_alias}.taggable_id = #{table_name}.#{primary_key} AND
         
     | 
| 
      
 205 
     | 
    
         
            +
                            #{taggings_alias}.taggable_type = #{quote_string_value(base_class.name)}
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                          INNER JOIN #{Tag.table_name} #{tags_alias} ON
         
     | 
| 
      
 208 
     | 
    
         
            +
                            #{taggings_alias}.tag_id = #{tags_alias}.id AND
         
     | 
| 
      
 209 
     | 
    
         
            +
                            #{tags_alias}.name = ?
         
     | 
| 
      
 210 
     | 
    
         
            +
                        END
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                        joins << sanitize_sql([join, tag])
         
     | 
| 
      
 213 
     | 
    
         
            +
                      end
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                      joins.join(' ')
         
     | 
| 
      
 216 
     | 
    
         
            +
                    end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                    # Calculate the tag counts for all tags.
         
     | 
| 
      
 219 
     | 
    
         
            +
                    #
         
     | 
| 
      
 220 
     | 
    
         
            +
                    # See Tag.counts for available options.
         
     | 
| 
      
 221 
     | 
    
         
            +
                    def tag_counts(options = {})
         
     | 
| 
      
 222 
     | 
    
         
            +
                      # Tag.find(:all, find_options_for_tag_counts(options))
         
     | 
| 
      
 223 
     | 
    
         
            +
                      opt = find_options_for_tag_counts(options)
         
     | 
| 
      
 224 
     | 
    
         
            +
                      Tag.select(opt[:select]).where(opt[:conditions]).joins(opt[:joins]).group(opt[:group]).having(opt[:having]).order(opt[:order]).limit(options[:limit])
         
     | 
| 
      
 225 
     | 
    
         
            +
                    end
         
     | 
| 
      
 226 
     | 
    
         
            +
                    alias_method :all_tag_counts, :tag_counts
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
                    def find_options_for_tag_counts(options = {})
         
     | 
| 
      
 229 
     | 
    
         
            +
                      options = options.dup
         
     | 
| 
      
 230 
     | 
    
         
            +
                      scope = scope_attributes
         
     | 
| 
      
 231 
     | 
    
         
            +
                      # scope(:find)
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                      conditions = []
         
     | 
| 
      
 234 
     | 
    
         
            +
                      conditions << send(:sanitize_sql_for_assignment, options.delete(:conditions)) if options[:conditions]
         
     | 
| 
      
 235 
     | 
    
         
            +
                      conditions << send(:sanitize_sql_for_assignment, scope) if scope
         
     | 
| 
      
 236 
     | 
    
         
            +
                      conditions << "#{Tagging.table_name}.taggable_type = #{quote_string_value(base_class.name)}"
         
     | 
| 
      
 237 
     | 
    
         
            +
                      conditions << type_condition unless descends_from_active_record?
         
     | 
| 
      
 238 
     | 
    
         
            +
                      conditions.delete('')
         
     | 
| 
      
 239 
     | 
    
         
            +
                      conditions.compact!
         
     | 
| 
      
 240 
     | 
    
         
            +
                      conditions = conditions.join(" AND ")
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
                      joins = ["INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"]
         
     | 
| 
      
 243 
     | 
    
         
            +
                      joins << options.delete(:joins) if options[:joins].present?
         
     | 
| 
      
 244 
     | 
    
         
            +
                      # joins << scope[:joins] if scope && scope[:joins].present?
         
     | 
| 
      
 245 
     | 
    
         
            +
                      joins = joins.join(" ")
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
                      options = { :conditions => conditions, :joins => joins }.update(options)
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
                      Tag.options_for_counts(options)
         
     | 
| 
      
 250 
     | 
    
         
            +
                    end
         
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
      
 252 
     | 
    
         
            +
                    def caching_tag_list?
         
     | 
| 
      
 253 
     | 
    
         
            +
                      column_names.include?(cached_tag_list_column_name)
         
     | 
| 
      
 254 
     | 
    
         
            +
                    end
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
                    private
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
                    def quote_string_value(object)
         
     | 
| 
      
 259 
     | 
    
         
            +
                      connection.quote(object)
         
     | 
| 
      
 260 
     | 
    
         
            +
                    end
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
                    def tags_condition(tags, table_name = Tag.table_name)
         
     | 
| 
      
 263 
     | 
    
         
            +
                      condition = tags.map { |t| sanitize_sql(["#{table_name}.name LIKE ?", t]) }.join(" OR ")
         
     | 
| 
      
 264 
     | 
    
         
            +
                      "(" + condition + ")" unless condition.blank?
         
     | 
| 
      
 265 
     | 
    
         
            +
                    end
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                    def merge_conditions(*conditions)
         
     | 
| 
      
 268 
     | 
    
         
            +
                      segments = []
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                      conditions.each do |condition|
         
     | 
| 
      
 271 
     | 
    
         
            +
                        unless condition.blank?
         
     | 
| 
      
 272 
     | 
    
         
            +
                          sql = sanitize_sql(condition)
         
     | 
| 
      
 273 
     | 
    
         
            +
                          segments << sql unless sql.blank?
         
     | 
| 
      
 274 
     | 
    
         
            +
                        end
         
     | 
| 
      
 275 
     | 
    
         
            +
                      end
         
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
      
 277 
     | 
    
         
            +
                      "(#{segments.join(') AND (')})" unless segments.empty?
         
     | 
| 
      
 278 
     | 
    
         
            +
                    end
         
     | 
| 
      
 279 
     | 
    
         
            +
                  end
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                  module InstanceMethods
         
     | 
| 
      
 282 
     | 
    
         
            +
                    def tag_list
         
     | 
| 
      
 283 
     | 
    
         
            +
                      return @tag_list if @tag_list ||= nil
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                      if self.class.caching_tag_list? && !(cached_value = send(self.class.cached_tag_list_column_name)).nil?
         
     | 
| 
      
 286 
     | 
    
         
            +
                        @tag_list = TagList.from(cached_value)
         
     | 
| 
      
 287 
     | 
    
         
            +
                      else
         
     | 
| 
      
 288 
     | 
    
         
            +
                        @tag_list = TagList.new(*tags.map(&:name))
         
     | 
| 
      
 289 
     | 
    
         
            +
                      end
         
     | 
| 
      
 290 
     | 
    
         
            +
                    end
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
                    def tag_list=(value)
         
     | 
| 
      
 293 
     | 
    
         
            +
                      @tag_list = TagList.from(value)
         
     | 
| 
      
 294 
     | 
    
         
            +
                    end
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
                    def save_cached_tag_list
         
     | 
| 
      
 297 
     | 
    
         
            +
                      if self.class.caching_tag_list?
         
     | 
| 
      
 298 
     | 
    
         
            +
                        self[self.class.cached_tag_list_column_name] = tag_list.to_s
         
     | 
| 
      
 299 
     | 
    
         
            +
                      end
         
     | 
| 
      
 300 
     | 
    
         
            +
                    end
         
     | 
| 
      
 301 
     | 
    
         
            +
             
     | 
| 
      
 302 
     | 
    
         
            +
                    #build list from related tags
         
     | 
| 
      
 303 
     | 
    
         
            +
                    def all_tags_list
         
     | 
| 
      
 304 
     | 
    
         
            +
                      tags.pluck(:name)
         
     | 
| 
      
 305 
     | 
    
         
            +
                    end
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
                    def save_tags
         
     | 
| 
      
 308 
     | 
    
         
            +
                      return unless @tag_list
         
     | 
| 
      
 309 
     | 
    
         
            +
             
     | 
| 
      
 310 
     | 
    
         
            +
                      new_tag_names = @tag_list - tags.map(&:name)
         
     | 
| 
      
 311 
     | 
    
         
            +
                      old_tags = tags.reject { |tag| @tag_list.include?(tag.name) }
         
     | 
| 
      
 312 
     | 
    
         
            +
             
     | 
| 
      
 313 
     | 
    
         
            +
                      self.class.transaction do
         
     | 
| 
      
 314 
     | 
    
         
            +
                        if old_tags.any?
         
     | 
| 
      
 315 
     | 
    
         
            +
                          taggings.where("tag_id IN (?)", old_tags.map(&:id)).each(&:destroy)
         
     | 
| 
      
 316 
     | 
    
         
            +
                          taggings.reset
         
     | 
| 
      
 317 
     | 
    
         
            +
                        end
         
     | 
| 
      
 318 
     | 
    
         
            +
                        new_tag_names.each do |new_tag_name|
         
     | 
| 
      
 319 
     | 
    
         
            +
                          tags << Tag.find_or_create_with_like_by_name(new_tag_name)
         
     | 
| 
      
 320 
     | 
    
         
            +
                        end
         
     | 
| 
      
 321 
     | 
    
         
            +
                      end
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
      
 323 
     | 
    
         
            +
                      true
         
     | 
| 
      
 324 
     | 
    
         
            +
                    end
         
     | 
| 
      
 325 
     | 
    
         
            +
             
     | 
| 
      
 326 
     | 
    
         
            +
                    # Calculate the tag counts for the tags used by this model.
         
     | 
| 
      
 327 
     | 
    
         
            +
                    #
         
     | 
| 
      
 328 
     | 
    
         
            +
                    # The possible options are the same as the tag_counts class method.
         
     | 
| 
      
 329 
     | 
    
         
            +
                    def tag_counts(options = {})
         
     | 
| 
      
 330 
     | 
    
         
            +
                      return [] if tag_list.blank?
         
     | 
| 
      
 331 
     | 
    
         
            +
             
     | 
| 
      
 332 
     | 
    
         
            +
                      options[:conditions] = self.class.send(:merge_conditions, options[:conditions], self.class.send(:tags_condition, tag_list))
         
     | 
| 
      
 333 
     | 
    
         
            +
                      self.class.tag_counts(options)
         
     | 
| 
      
 334 
     | 
    
         
            +
                    end
         
     | 
| 
      
 335 
     | 
    
         
            +
             
     | 
| 
      
 336 
     | 
    
         
            +
                    def reload_with_tag_list(*args) #:nodoc:
         
     | 
| 
      
 337 
     | 
    
         
            +
                      @tag_list = nil
         
     | 
| 
      
 338 
     | 
    
         
            +
                      reload_without_tag_list(*args)
         
     | 
| 
      
 339 
     | 
    
         
            +
                    end
         
     | 
| 
      
 340 
     | 
    
         
            +
                  end
         
     | 
| 
      
 341 
     | 
    
         
            +
                end
         
     | 
| 
      
 342 
     | 
    
         
            +
              end
         
     | 
| 
      
 343 
     | 
    
         
            +
            end
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
            ActiveRecord::Base.send(:include, YeshouaCrm::ActsAsTaggable::Taggable)
         
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
      
 347 
     | 
    
         
            +
            # Class aliases
         
     | 
| 
      
 348 
     | 
    
         
            +
            YeshouaCrm::Tag = YeshouaCrm::ActsAsTaggable::Tag
         
     | 
| 
      
 349 
     | 
    
         
            +
            YeshouaCrm::TagList = YeshouaCrm::ActsAsTaggable::TagList
         
     | 
| 
      
 350 
     | 
    
         
            +
            YeshouaCrm::Tagging = YeshouaCrm::ActsAsTaggable::Tagging
         
     | 
| 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module YeshouaCrm
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ActsAsTaggable #:nodoc:
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Tag < ActiveRecord::Base #:nodoc:
         
     | 
| 
      
 4 
     | 
    
         
            +
                  has_many :taggings, :dependent => :destroy
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  validates_presence_of :name
         
     | 
| 
      
 7 
     | 
    
         
            +
                  validates :name, :uniqueness => { :message => " not uniq tag" }
         
     | 
| 
      
 8 
     | 
    
         
            +
                  validates :name, :presence => true
         
     | 
| 
      
 9 
     | 
    
         
            +
                  cattr_accessor :destroy_unused
         
     | 
| 
      
 10 
     | 
    
         
            +
                  self.destroy_unused = false
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  attr_accessible :name if defined?(ActiveModel::MassAssignmentSecurity)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  # LIKE is used for cross-database case-insensitivity
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def self.find_or_create_with_like_by_name(name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    # find(:first, :conditions => ["name LIKE ?", name]) || create(:name => name)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    where("LOWER(name) LIKE LOWER(?)", name).first || create(:name => name)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def ==(object)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    super || (object.is_a?(Tag) && name == object.name)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def to_s
         
     | 
| 
      
 25 
     | 
    
         
            +
                    name
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def count
         
     | 
| 
      
 29 
     | 
    
         
            +
                    read_attribute(:count).to_i
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 33 
     | 
    
         
            +
                    # Calculate the tag counts for all tags.
         
     | 
| 
      
 34 
     | 
    
         
            +
                    #  :start_at - Restrict the tags to those created after a certain time
         
     | 
| 
      
 35 
     | 
    
         
            +
                    #  :end_at - Restrict the tags to those created before a certain time
         
     | 
| 
      
 36 
     | 
    
         
            +
                    #  :conditions - A piece of SQL conditions to add to the query
         
     | 
| 
      
 37 
     | 
    
         
            +
                    #  :limit - The maximum number of tags to return
         
     | 
| 
      
 38 
     | 
    
         
            +
                    #  :order - A piece of SQL to order by. Eg 'count desc' or 'taggings.created_at desc'
         
     | 
| 
      
 39 
     | 
    
         
            +
                    #  :at_least - Exclude tags with a frequency less than the given value
         
     | 
| 
      
 40 
     | 
    
         
            +
                    #  :at_most - Exclude tags with a frequency greater than the given value
         
     | 
| 
      
 41 
     | 
    
         
            +
                    def counts(options = {})
         
     | 
| 
      
 42 
     | 
    
         
            +
                      opt = options_for_counts(options)
         
     | 
| 
      
 43 
     | 
    
         
            +
                      select(opt[:select]).where(opt[:conditions]).joins(opt[:joins]).group(opt[:group])
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    def options_for_counts(options = {})
         
     | 
| 
      
 47 
     | 
    
         
            +
                      options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :joins
         
     | 
| 
      
 48 
     | 
    
         
            +
                      options = options.dup
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                      start_at = sanitize_sql(["#{Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
         
     | 
| 
      
 51 
     | 
    
         
            +
                      end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                      conditions = [
         
     | 
| 
      
 54 
     | 
    
         
            +
                        (sanitize_sql(options.delete(:conditions)) if options[:conditions]),
         
     | 
| 
      
 55 
     | 
    
         
            +
                        start_at,
         
     | 
| 
      
 56 
     | 
    
         
            +
                        end_at
         
     | 
| 
      
 57 
     | 
    
         
            +
                      ].compact
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                      conditions = conditions.join(' AND ') if conditions.any?
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                      joins = ["INNER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
         
     | 
| 
      
 62 
     | 
    
         
            +
                      joins << options.delete(:joins) if options[:joins]
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                      at_least  = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
         
     | 
| 
      
 65 
     | 
    
         
            +
                      at_most   = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
         
     | 
| 
      
 66 
     | 
    
         
            +
                      having    = 'COUNT(*) > 0'
         
     | 
| 
      
 67 
     | 
    
         
            +
                      having    = [having, at_least, at_most].compact.join(' AND ')
         
     | 
| 
      
 68 
     | 
    
         
            +
                      group_by  = "#{Tag.table_name}.id, #{Tag.table_name}.name"
         
     | 
| 
      
 69 
     | 
    
         
            +
                      # group_by << " AND #{having}" unless having.blank?
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                      { :select     => "#{Tag.table_name}.id, #{Tag.table_name}.name, COUNT(*) AS count",
         
     | 
| 
      
 72 
     | 
    
         
            +
                        :joins      => joins.join(" "),
         
     | 
| 
      
 73 
     | 
    
         
            +
                        :conditions => conditions,
         
     | 
| 
      
 74 
     | 
    
         
            +
                        :group      => group_by,
         
     | 
| 
      
 75 
     | 
    
         
            +
                        :having     => having
         
     | 
| 
      
 76 
     | 
    
         
            +
                      }.update(options)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,111 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module YeshouaCrm
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ActsAsTaggable #:nodoc:
         
     | 
| 
      
 3 
     | 
    
         
            +
                class TagList < Array #:nodoc:
         
     | 
| 
      
 4 
     | 
    
         
            +
                  cattr_accessor :delimiter
         
     | 
| 
      
 5 
     | 
    
         
            +
                  self.delimiter = ','
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def initialize(*args)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    add(*args)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  # Add tags to the tag_list. Duplicate or blank tags will be ignored.
         
     | 
| 
      
 12 
     | 
    
         
            +
                  #
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #   tag_list.add("Fun", "Happy")
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # Use the <tt>:parse</tt> option to add an unparsed tag string.
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #   tag_list.add("Fun, Happy", :parse => true)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  def add(*names)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    extract_and_apply_options!(names)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    concat(names)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    clean!
         
     | 
| 
      
 22 
     | 
    
         
            +
                    self
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  # Remove specific tags from the tag_list.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #   tag_list.remove("Sad", "Lonely")
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #   tag_list.remove("Sad, Lonely", :parse => true)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  def remove(*names)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    extract_and_apply_options!(names)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    delete_if { |name| names.include?(name) }
         
     | 
| 
      
 35 
     | 
    
         
            +
                    self
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  # Toggle the presence of the given tags.
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # If a tag is already in the list it is removed, otherwise it is added.
         
     | 
| 
      
 40 
     | 
    
         
            +
                  def toggle(*names)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    extract_and_apply_options!(names)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    names.each do |name|
         
     | 
| 
      
 44 
     | 
    
         
            +
                      include?(name) ? delete(name) : push(name)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    clean!
         
     | 
| 
      
 48 
     | 
    
         
            +
                    self
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  # Transform the tag_list into a tag string suitable for edting in a form.
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  #
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #   tag_list = TagList.new("Round", "Square,Cube")
         
     | 
| 
      
 55 
     | 
    
         
            +
                  #   tag_list.to_s # 'Round, "Square,Cube"'
         
     | 
| 
      
 56 
     | 
    
         
            +
                  def to_s
         
     | 
| 
      
 57 
     | 
    
         
            +
                    clean!
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    map do |name|
         
     | 
| 
      
 60 
     | 
    
         
            +
                      name.include?(delimiter) ? "\"#{name}\"" : name
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                 private
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # Remove whitespace, duplicates, and blanks.
         
     | 
| 
      
 66 
     | 
    
         
            +
                  def clean!
         
     | 
| 
      
 67 
     | 
    
         
            +
                    reject!(&:blank?)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    map!(&:strip)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    uniq!
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def extract_and_apply_options!(args)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    options = args.last.is_a?(Hash) ? args.pop : {}
         
     | 
| 
      
 74 
     | 
    
         
            +
                    options.assert_valid_keys :parse
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    args.map! { |a| self.class.from(a) } if options[:parse]
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    args.flatten!
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 82 
     | 
    
         
            +
                    # Returns a new TagList using the given tag string.
         
     | 
| 
      
 83 
     | 
    
         
            +
                    #
         
     | 
| 
      
 84 
     | 
    
         
            +
                    #   tag_list = TagList.from("One , Two,  Three")
         
     | 
| 
      
 85 
     | 
    
         
            +
                    #   tag_list # ["One", "Two", "Three"]
         
     | 
| 
      
 86 
     | 
    
         
            +
                    def from(source)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      tag_list = new
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                      case source
         
     | 
| 
      
 90 
     | 
    
         
            +
                      when Array
         
     | 
| 
      
 91 
     | 
    
         
            +
                        tag_list.add(source)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      else
         
     | 
| 
      
 93 
     | 
    
         
            +
                        string = source.to_s.dup
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                        # Parse the quoted tags
         
     | 
| 
      
 96 
     | 
    
         
            +
                        [
         
     | 
| 
      
 97 
     | 
    
         
            +
                          /\s*#{delimiter}\s*(['"])(.*?)\1\s*/,
         
     | 
| 
      
 98 
     | 
    
         
            +
                          /^\s*(['"])(.*?)\1\s*#{delimiter}?/
         
     | 
| 
      
 99 
     | 
    
         
            +
                        ].each do |re|
         
     | 
| 
      
 100 
     | 
    
         
            +
                          string.gsub!(re) { tag_list << $2; "" }
         
     | 
| 
      
 101 
     | 
    
         
            +
                        end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                        tag_list.add(string.split(delimiter))
         
     | 
| 
      
 104 
     | 
    
         
            +
                      end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                      tag_list
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
              end
         
     | 
| 
      
 111 
     | 
    
         
            +
            end
         
     |