diff --git a/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js b/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js
index 37245d25a..d0176b28c 100644
--- a/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js
+++ b/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js
@@ -37,8 +37,9 @@ Discourse.TopicBulkActionsController = Ember.ArrayController.extend(Discourse.Mo
 
     changeCategory: function() {
       var category = Discourse.Category.findById(parseInt(this.get('newCategoryId'), 10)),
+          categoryName = (category ? category.get('name') : null),
           self = this;
-      this.perform({type: 'change_category', category_id: this.get('newCategoryId')}).then(function(topics) {
+      this.perform({type: 'change_category', category_name: categoryName}).then(function(topics) {
         topics.forEach(function(t) {
           t.set('category', category);
         });
diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb
index f0e0dc91c..e4dcc6535 100644
--- a/lib/topics_bulk_action.rb
+++ b/lib/topics_bulk_action.rb
@@ -6,9 +6,34 @@ class TopicsBulkAction
     @operation = operation
   end
 
-  def perform!
-    []
+  def self.operations
+    %w(change_category)
   end
 
+  def perform!
+    raise Discourse::InvalidParameters.new(:operation) unless TopicsBulkAction.operations.include?(@operation[:type])
+    send(@operation[:type])
+  end
+
+  private
+
+    def change_category
+      changed_ids = []
+      topics.each do |t|
+        if guardian.can_edit?(t)
+          changed_ids << t.id if t.change_category(@operation[:category_name])
+        end
+      end
+      changed_ids
+    end
+
+    def guardian
+      @guardian ||= Guardian.new(@user)
+    end
+
+    def topics
+      @topics ||= Topic.where(id: @topic_ids)
+    end
+
 end
 
diff --git a/spec/components/topics_bulk_action_spec.rb b/spec/components/topics_bulk_action_spec.rb
new file mode 100644
index 000000000..3e0d3b7f0
--- /dev/null
+++ b/spec/components/topics_bulk_action_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+require 'topics_bulk_action'
+
+describe TopicsBulkAction do
+
+  describe "invalid operation" do
+    let(:user) { Fabricate.build(:user) } 
+
+    it "raises an error with an invalid operation" do
+      tba = TopicsBulkAction.new(user, [1], type: 'rm_root')
+      -> { tba.perform! }.should raise_error(Discourse::InvalidParameters)
+    end
+  end
+
+  describe "change_category" do
+    let(:topic) { Fabricate(:topic) }
+    let(:category) { Fabricate(:category) } 
+
+    context "when the user can edit the topic" do
+      it "changes the category and returns the topic_id" do
+        tba = TopicsBulkAction.new(topic.user, [topic.id], type: 'change_category', category_name: category.name)
+        topic_ids = tba.perform!
+        topic_ids.should == [topic.id]
+        topic.reload
+        topic.category.should == category
+      end
+    end
+
+    context "when the user can't edit the topic" do
+      it "doesn't change the category and returns the topic_id" do
+        Guardian.any_instance.expects(:can_edit?).returns(false)
+        tba = TopicsBulkAction.new(topic.user, [topic.id], type: 'change_category', category_name: category.name)
+        topic_ids = tba.perform!
+        topic_ids.should == []
+        topic.reload
+        topic.category.should_not == category
+      end
+    end
+
+  end
+end
+