From c1bcfef90c3a93a6a7f557794d0c73fff079c3b2 Mon Sep 17 00:00:00 2001
From: Raphael Sofaer <raphael@joindiaspora.com>
Date: Thu, 10 Mar 2011 16:54:44 -0800
Subject: [PATCH] TAGS ARE SO COOL

---
 Gemfile                                       |  6 +--
 Gemfile.lock                                  |  4 +-
 app/models/status_message.rb                  | 21 +++++++++
 ...311000150_acts_as_taggable_on_migration.rb | 28 +++++++++++
 db/schema.rb                                  | 19 +++++++-
 spec/models/status_message_spec.rb            | 46 +++++++++++++++++++
 6 files changed, 118 insertions(+), 6 deletions(-)
 create mode 100644 db/migrate/20110311000150_acts_as_taggable_on_migration.rb

diff --git a/Gemfile b/Gemfile
index 61f2ac22d1..38ea5ec327 100644
--- a/Gemfile
+++ b/Gemfile
@@ -24,12 +24,12 @@ gem 'twitter', :git => 'git://github.com/jnunemaker/twitter.git', :ref => 'ef122
 gem 'haml', '3.0.25'
 gem 'will_paginate', '3.0.pre2'
 
-#Statistics
-gem 'googlecharts'
-
 #Inflected translations
 gem 'i18n-inflector-rails', '~> 1.0'
 
+#Tags
+gem 'acts-as-taggable-on', '2.0.6'
+
 #Uncatagorized
 gem 'roxml', :git => 'git://github.com/Empact/roxml.git', :ref => '7ea9a9ffd2338aaef5b0'
 gem 'addressable', '2.2.2', :require => 'addressable/uri'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2f85205a28..cd611be097 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -86,6 +86,7 @@ GEM
       activemodel (= 3.0.3)
       activesupport (= 3.0.3)
     activesupport (3.0.3)
+    acts-as-taggable-on (2.0.6)
     addressable (2.2.2)
     arel (2.0.9)
     aws (2.3.32)
@@ -189,7 +190,6 @@ GEM
     gem_plugin (0.2.3)
     gherkin (2.3.3)
       json (~> 1.4.6)
-    googlecharts (1.6.1)
     haml (3.0.25)
     hashie (0.4.0)
     highline (1.6.1)
@@ -387,6 +387,7 @@ PLATFORMS
 
 DEPENDENCIES
   SystemTimer (= 1.2.1)
+  acts-as-taggable-on (= 2.0.6)
   addressable (= 2.2.2)
   aws (= 2.3.32)
   bundler (>= 1.0.0)
@@ -408,7 +409,6 @@ DEPENDENCIES
   fog (= 0.3.25)
   foreigner (= 0.9.1)
   fuubar
-  googlecharts
   haml (= 3.0.25)
   http_accept_language!
   i18n-inflector-rails (~> 1.0)
diff --git a/app/models/status_message.rb b/app/models/status_message.rb
index 224d14cd43..7aa0e5add6 100644
--- a/app/models/status_message.rb
+++ b/app/models/status_message.rb
@@ -8,6 +8,9 @@ class StatusMessage < Post
   require File.join(Rails.root, 'lib/youtube_titles')
   include ActionView::Helpers::TextHelper
 
+  acts_as_taggable
+  acts_as_taggable_on :tags
+
   validates_length_of :message, :maximum => 1000, :message => "please make your status messages less than 1000 characters"
   xml_name :status_message
   xml_attr :raw_message
@@ -22,6 +25,8 @@ class StatusMessage < Post
     get_youtube_title message
   end
 
+  before_create :build_tags
+
   def message(opts = {})
     self.formatted_message(opts)
   end
@@ -84,6 +89,22 @@ class StatusMessage < Post
     identifiers.empty? ? [] : Person.where(:diaspora_handle => identifiers)
   end
 
+  def build_tags
+    self.tag_list = tag_strings
+  end
+
+  def tag_strings
+    regex = /(^|\s)#(\w+)/
+    matches = self.raw_message.scan(regex).map do |match|
+      match.last
+    end
+    unique_matches = matches.inject(Hash.new) do |h,element|
+      h[element.downcase] = element unless h[element.downcase]
+      h
+    end
+    unique_matches.values
+  end
+
   def to_activity
     <<-XML
   <entry>
diff --git a/db/migrate/20110311000150_acts_as_taggable_on_migration.rb b/db/migrate/20110311000150_acts_as_taggable_on_migration.rb
new file mode 100644
index 0000000000..7c07fef22e
--- /dev/null
+++ b/db/migrate/20110311000150_acts_as_taggable_on_migration.rb
@@ -0,0 +1,28 @@
+class ActsAsTaggableOnMigration < ActiveRecord::Migration
+  def self.up
+    create_table :tags do |t|
+      t.string :name
+    end
+
+    create_table :taggings do |t|
+      t.references :tag
+
+      # You should make sure that the column created is
+      # long enough to store the required class names.
+      t.references :taggable, :polymorphic => {:limit => 127}
+      t.references :tagger, :polymorphic => {:limit => 127}
+
+      t.string :context, :limit => 127
+
+      t.datetime :created_at
+    end
+
+    add_index :taggings, :tag_id
+    add_index :taggings, [:taggable_id, :taggable_type, :context]
+  end
+
+  def self.down
+    drop_table :taggings
+    drop_table :tags
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 051abe6f70..5f818163d3 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20110301202619) do
+ActiveRecord::Schema.define(:version => 20110311000150) do
 
   create_table "aspect_memberships", :force => true do |t|
     t.integer  "aspect_id",  :null => false
@@ -459,6 +459,23 @@ ActiveRecord::Schema.define(:version => 20110301202619) do
   add_index "services", ["mongo_id"], :name => "index_services_on_mongo_id"
   add_index "services", ["user_id"], :name => "index_services_on_user_id"
 
+  create_table "taggings", :force => true do |t|
+    t.integer  "tag_id"
+    t.integer  "taggable_id"
+    t.string   "taggable_type", :limit => 127
+    t.integer  "tagger_id"
+    t.string   "tagger_type",   :limit => 127
+    t.string   "context",       :limit => 127
+    t.datetime "created_at"
+  end
+
+  add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
+  add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
+
+  create_table "tags", :force => true do |t|
+    t.string "name"
+  end
+
   create_table "users", :force => true do |t|
     t.string   "username"
     t.text     "serialized_private_key"
diff --git a/spec/models/status_message_spec.rb b/spec/models/status_message_spec.rb
index afd7eddb77..e71e3df0ba 100644
--- a/spec/models/status_message_spec.rb
+++ b/spec/models/status_message_spec.rb
@@ -18,6 +18,11 @@ describe StatusMessage do
       status.should_receive(:create_mentions)
       status.save
     end
+    it 'calls build_tags' do
+      status = Factory.build(:status_message)
+      status.should_receive(:build_tags)
+      status.save
+    end
   end
 
   describe '#diaspora_handle=' do
@@ -103,6 +108,9 @@ STR
       it 'escapes the link title' do
         p = @people[0].profile
         p.first_name="</a><script>alert('h')</script>"
+["a", "b", "A", "C"]\
+.inject(Hash.new){ |h,element| h[element.downcase] = element  unless h[element.downcase]  ; h }\
+.values
         p.save!
 
         @sm.formatted_message.should_not include(@people[0].profile.first_name)
@@ -165,6 +173,44 @@ STR
       end
     end
   end
+  describe 'tags' do
+    before do
+      @sm = Factory.build(:status_message)
+    end
+    describe '#build_tags' do
+      it 'builds the tags' do
+        @sm.message = '#what'
+        @sm.build_tags
+        @sm.tag_list.should == ['what']
+        lambda {
+          @sm.save
+        }.should change{@sm.tags.count}.by(1)
+      end
+    end
+    describe '#tag_strings' do
+      it 'returns a string for every #thing' do
+        str = '#what #hey #that"smybike. #@hey ##boo # #THATWASMYBIKE #hey#there #135440we #abc/23 ###'
+        arr = ['what', 'hey', 'that', 'THATWASMYBIKE', '135440we', 'abc']
+
+        @sm.message = str
+        @sm.tag_strings.should =~ arr
+      end
+      it 'returns no duplicates' do
+        str = '#what #what #what #whaaaaaaaaaat'
+        arr = ['what','whaaaaaaaaaat']
+
+        @sm.message = str
+        @sm.tag_strings.should =~ arr
+      end
+      it 'is case insensitive' do
+        str = '#what #wHaT #WHAT'
+        arr = ['what']
+
+        @sm.message = str
+        @sm.tag_strings.should =~ arr
+      end
+    end
+  end
   describe "XML" do
     before do
       @message = Factory.create(:status_message, :message => "I hate WALRUSES!", :author => @user.person)
-- 
GitLab