├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── acts_as_commentable_with_threading.gemspec ├── gemfiles ├── Gemfile.rails-4.0 ├── Gemfile.rails-4.1 ├── Gemfile.rails-4.2 └── Gemfile.rails-5.0 ├── init.rb ├── install.rb ├── lib ├── acts_as_commentable_with_threading.rb └── generators │ ├── acts_as_commentable_upgrade_migration │ ├── USAGE │ ├── acts_as_commentable_upgrade_migration_generator.rb │ ├── comment.rb │ └── templates │ │ └── migration.rb │ └── acts_as_commentable_with_threading_migration │ ├── USAGE │ ├── acts_as_commentable_with_threading_migration_generator.rb │ └── templates │ ├── comment.rb │ └── migration.rb ├── rails └── init.rb └── spec ├── comment_spec.rb ├── commentable_spec.rb ├── db ├── database.yml └── schema.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .rvmrc 2 | .ruby-version 3 | gemfiles/*.lock 4 | pkg/* 5 | spec/debug.log 6 | Gemfile.lock 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - gem update bundler 3 | gemfile: 4 | - gemfiles/Gemfile.rails-5.0 5 | - gemfiles/Gemfile.rails-4.2 6 | - gemfiles/Gemfile.rails-4.1 7 | - gemfiles/Gemfile.rails-4.0 8 | rvm: 9 | - 2.3.0 10 | - 2.2.3 11 | - 2.1.0 12 | - 2.0.0 13 | - 1.9.3 14 | - jruby-19mode 15 | - rbx-2 16 | matrix: 17 | exclude: 18 | - gemfile: gemfiles/Gemfile.rails-5.0 19 | rvm: rbx-2 20 | - gemfile: gemfiles/Gemfile.rails-5.0 21 | rvm: jruby-19mode 22 | - gemfile: gemfiles/Gemfile.rails-5.0 23 | rvm: 1.9.3 24 | - gemfile: gemfiles/Gemfile.rails-5.0 25 | rvm: 2.0.0 26 | - gemfile: gemfiles/Gemfile.rails-5.0 27 | rvm: 2.1.0 28 | allow_failures: 29 | - rvm: rbx-2 30 | - gemfile: gemfiles/Gemfile.rails-4.1 31 | rvm: jruby-19mode 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v2.0.1 2 | ------ 3 | 4 | - Minor change to eliminate warning when used with Rails 5.x 5 | 6 | v2.0.0 7 | ------ 8 | 9 | - Removed support for Rails 3.x - gem only supports Rails 4+ going forward. 10 | - Updated rspec versions to 3.x and switch to 'expect' syntax. 11 | - Removed default values from referencing id columns in `Comment` class. 12 | - Added missing '.rb' suffix on migration files. 13 | 14 | v1.2.0 15 | ------ 16 | 17 | - Updated rspec versions to 2.x and make corresponding changes to Rakefile. 18 | - Removed sqlite3 development dependency to support JRuby in development/test. 19 | - Added initial Rails 4 support, subject to acts_as_nested_set dependency. 20 | - Removed empty string default values for string columns in `Comment`. 21 | 22 | v1.1.3 23 | ------ 24 | 25 | - Fixed typos in acts_as_votable integration comment and instructions. 26 | - Updated commentable to call `destroy_all` on `root_comments`, rather than on all comments (to ensure comments are properly deleted). 27 | - Fixed polymorphic index to include `commentable_type`. 28 | - Update Comment to use `validates` rather than the deprecated `validates_presence_of`. 29 | 30 | v1.1.2 31 | ------ 32 | 33 | - Updated awesome_nested_set dependency to account for change in repository location. 34 | - Fixed Comment so it stores the correct class when a commentable is an STL child class. 35 | - Added commentable association to Comment. 36 | - Assorted README changes. 37 | 38 | v1.1.1 39 | ------ 40 | 41 | - Removed length limit on Commentable class name. 42 | - Removed default order from `comment_threads` 43 | 44 | v1.1.0 45 | ------ 46 | 47 | - Updated to Rails 3+. 48 | - First Rubygems release. 49 | 50 | v0.1.2 51 | ------ 52 | 53 | - Added new documentation in README that should better help users understand this plugin. 54 | - Added new instance method `root_comments` that will return all comments belonging to a model except child comments. 55 | - Added new class method for Comment: `self.build_from(object, user_id, comment_text:string)`. This method cleans up the code inside the controllers. 56 | - Added new class method for Comment: `has_children?` that will return true or false depending on if a comment has children. 57 | - Updated deprecated code in instance method `comments_ordered_by_submitted` - changed `self.type.name` to `self.class.name`. 58 | 59 | v0.1.1 60 | ------ 61 | 62 | - Fixes to gemspec. 63 | 64 | v0.1.0 65 | ------ 66 | 67 | - Initial Release. 68 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'activerecord-jdbcsqlite3-adapter', platforms: :jruby 7 | gem 'sqlite3', platforms: :ruby 8 | end 9 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Evan David Light 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/elight/acts_as_commentable_with_threading.png)](https://travis-ci.org/elight/acts_as_commentable_with_threading) 2 | [![Code Climate](https://codeclimate.com/github/elight/acts_as_commentable_with_threading/badges/gpa.svg)](https://codeclimate.com/github/elight/acts_as_commentable_with_threading) 3 | 4 | Acts As Commentable (now with comment threads(TM)!!! -- kidding on the (TM)) 5 | =================== 6 | 7 | Allows for threaded comments to be added to multiple and different models. 8 | Drop-in compatible for acts_as_commentable (however requiring a database 9 | schema change) 10 | 11 | Requirements 12 | ------------ 13 | The 2.x version of this gem is for Rails 4 and later versions only. For the Rails 3.x compatible version of this gem, please use version 1.2.0. 14 | 15 | This gem depends on CollectiveIdea's Awesome Nested Set gem. It is installed if 16 | not already present when you install this gem. 17 | 18 | You can find the gem on GitHub at [collectiveidea/awesome_nested_set] 19 | 20 | [collectiveidea/awesome_nested_set]: https://github.com/collectiveidea/awesome_nested_set 21 | 22 | Install 23 | ------- 24 | In your Gemfile, add: 25 | 26 | gem 'acts_as_commentable_with_threading' 27 | 28 | and run `bundle install`. 29 | 30 | Migrations 31 | ---------- 32 | * To install from scratch: 33 | 34 | rails generate acts_as_commentable_with_threading_migration 35 | 36 | This will generate the migration script necessary for the table 37 | 38 | * To upgrade to acts_as_commentable_with_threading from the 39 | old acts_as_commentable: 40 | 41 | rails generate acts_as_commentable_upgrade_migration 42 | 43 | This will generate the necessary migration to upgrade your comments 44 | table to work with acts_as_commentable_with_threading 45 | 46 | If the generators fail, you can just as easily create the migrations by hand. 47 | See the templates in the generators under [`lib/generators`]. 48 | 49 | [`lib/generators`]: https://github.com/elight/acts_as_commentable_with_threading/tree/master/lib/generators 50 | 51 | Usage 52 | ----- 53 | class Article < ActiveRecord::Base 54 | acts_as_commentable 55 | end 56 | 57 | * Add a comment to a model instance, for example an Article: 58 | 59 | @article = Article.find(params[:id]) 60 | @user_who_commented = @current_user 61 | @comment = Comment.build_from( @article, @user_who_commented.id, "Hey guys this is my comment!" ) 62 | 63 | * To make a newly created comment into a child/reply of another comment: 64 | 65 | @comment.move_to_child_of(the_desired_parent_comment) 66 | 67 | * To retrieve all comments for an article, including child comments: 68 | 69 | @all_comments = @article.comment_threads 70 | 71 | * To retrieve only the root comments without their child comments: 72 | 73 | @root_comments = @article.root_comments 74 | 75 | * To check if a comment has children: 76 | 77 | @comment.has_children? 78 | 79 | * To verify the number of children a comment has: 80 | 81 | @comment.children.size 82 | 83 | * To retrieve a comment's children: 84 | 85 | @comment.children 86 | 87 | * If you plan to use the `acts_as_votable` plugin with your comment system be 88 | sure to uncomment the line [`acts_as_votable`][L9] in `lib/comment.rb`. 89 | 90 | [L9]: https://github.com/elight/acts_as_commentable_with_threading/blob/master/lib/generators/acts_as_commentable_with_threading_migration/templates/comment.rb#L9 91 | 92 | Credits 93 | ------- 94 | * [xxx](https://github.com/xxx) - For contributing the updates for Rails 3! 95 | * [Jack Dempsey](https://github.com/jackdempsey) - This plugin/gem is heavily 96 | influenced/liberally borrowed/stolen from [acts_as_commentable]. 97 | 98 | And in turn... 99 | 100 | * Xelipe - Because acts_as_commentable was heavily influenced by Acts As Taggable. 101 | 102 | [acts_as_commentable]: https://github.com/jackdempsey/acts_as_commentable 103 | 104 | More 105 | ---- 106 | * [http://tripledogdare.net](http://tripledogdare.net) 107 | * [http://evan.tiggerpalace.com](http://evan.tiggerpalace.com) 108 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rspec/core/rake_task' 5 | 6 | desc 'Run specs' 7 | RSpec::Core::RakeTask.new(:spec) 8 | 9 | task install: [:package] do 10 | sh %(sudo gem install pkg/#{NAME}-#{GEM_VERSION}) 11 | end 12 | 13 | desc 'Default: run specs.' 14 | task default: :spec 15 | -------------------------------------------------------------------------------- /acts_as_commentable_with_threading.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'acts_as_commentable_with_threading' 3 | s.version = '2.0.1' 4 | s.date = '2015-12-22' 5 | s.summary = 'Polymorphic comments Rails gem - Rails 4+ only' 6 | s.email = 'evan@tripledogdare.net' 7 | s.homepage = 'http://github.com/elight/acts_as_commentable_with_threading' 8 | s.description = 'Polymorphic threaded comments Rails gem for Rails 4+' 9 | s.authors = ['Evan Light', 'Jack Dempsey', 'Xelipe', 'xxx'] 10 | s.files = `git ls-files`.split("\n") 11 | s.test_files = `git ls-files -- spec/*`.split("\n") 12 | 13 | s.add_development_dependency 'rake' 14 | s.add_development_dependency 'rspec', '>= 3.0' 15 | s.add_development_dependency 'rails', '>= 4.0' 16 | 17 | s.add_dependency 'activerecord', '>= 4.0' 18 | s.add_dependency 'activesupport', '>= 4.0' 19 | s.add_dependency 'awesome_nested_set', '>= 3.0' 20 | end 21 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-4.0: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec path: '..' 4 | 5 | group :development do 6 | gem 'activerecord-jdbcsqlite3-adapter', :platforms => :jruby 7 | gem 'sqlite3', :platforms => :ruby 8 | gem 'rails', '~> 4.0.0' 9 | end 10 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-4.1: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec path: '..' 4 | 5 | group :development do 6 | gem 'activerecord-jdbcsqlite3-adapter', :platforms => :jruby 7 | gem 'sqlite3', :platforms => :ruby 8 | gem 'rails', '~> 4.1.0' 9 | end 10 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-4.2: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec path: '..' 4 | 5 | group :development do 6 | gem 'activerecord-jdbcsqlite3-adapter', :platforms => :jruby 7 | gem 'sqlite3', :platforms => :ruby 8 | gem 'rails', '~> 4.2.0' 9 | end 10 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-5.0: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec path: '..' 4 | 5 | group :development do 6 | gem 'activerecord-jdbcsqlite3-adapter', platforms: :jruby 7 | gem 'sqlite3', platforms: :ruby 8 | gem 'rails', '~> 5.0.0.beta1' 9 | end 10 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('./rails/init', File.dirname(__FILE__)) 2 | -------------------------------------------------------------------------------- /install.rb: -------------------------------------------------------------------------------- 1 | # Install hook code here 2 | -------------------------------------------------------------------------------- /lib/acts_as_commentable_with_threading.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'awesome_nested_set' 3 | ActiveRecord::Base.class_eval do 4 | include CollectiveIdea::Acts::NestedSet 5 | end 6 | 7 | # 8 | unless ActiveRecord::Base.respond_to?(:acts_as_nested_set) 9 | ActiveRecord::Base.send(:include, CollectiveIdea::Acts::NestedSet::Base) 10 | end 11 | 12 | # ActsAsCommentableWithThreading 13 | module Acts #:nodoc: 14 | module CommentableWithThreading #:nodoc: 15 | extend ActiveSupport::Concern 16 | 17 | module ClassMethods 18 | def acts_as_commentable 19 | has_many :comment_threads, class_name: 'Comment', as: :commentable 20 | before_destroy { |record| record.root_comments.destroy_all } 21 | include Acts::CommentableWithThreading::LocalInstanceMethods 22 | extend Acts::CommentableWithThreading::SingletonMethods 23 | end 24 | end 25 | 26 | # This module contains class methods 27 | module SingletonMethods 28 | # Helper method to lookup for comments for a given object. 29 | # This method is equivalent to obj.comments. 30 | def find_comments_for(obj) 31 | Comment.where(commentable_id: obj.id, 32 | commentable_type: obj.class.base_class.name) 33 | .order('created_at DESC') 34 | end 35 | 36 | # Helper class method to lookup comments for 37 | # the mixin commentable type written by a given user. 38 | # This method is NOT equivalent to Comment.find_comments_for_user 39 | def find_comments_by_user(user) 40 | commentable = base_class.name.to_s 41 | Comment.where(user_id: user.id, commentable_type: commentable) 42 | .order('created_at DESC') 43 | end 44 | end 45 | 46 | module LocalInstanceMethods 47 | # Helper method to display only root threads, no children/replies 48 | def root_comments 49 | comment_threads.where(parent_id: nil) 50 | end 51 | 52 | # Helper method to sort comments by date 53 | def comments_ordered_by_submitted 54 | Comment.where(commentable_id: id, commentable_type: self.class.name) 55 | .order('created_at DESC') 56 | end 57 | 58 | # Helper method that defaults the submitted time. 59 | def add_comment(comment) 60 | comments << comment 61 | end 62 | end 63 | end 64 | end 65 | 66 | ActiveRecord::Base.send(:include, Acts::CommentableWithThreading) 67 | -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_upgrade_migration/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | run rails generate acts_as_commentable_upgrade_migration 3 | 4 | no need to specify any parameters after acts_as_commentable_upgrade_migration as the generator just creates a migration to update your existing comments table 5 | the acts_as_commentable_upgrade_migration file will be created in db/migrate -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_upgrade_migration/acts_as_commentable_upgrade_migration_generator.rb: -------------------------------------------------------------------------------- 1 | class ActsAsCommentableUpgradeMigrationGenerator < Rails::Generators::Base 2 | include Rails::Generators::Migration 3 | 4 | source_root File.expand_path('../templates', __FILE__) 5 | 6 | # *for the life of me*, i can't figure out why this isn't 7 | # implemented elsewhere. 8 | def self.next_migration_number(dirname) 9 | if ActiveRecord::Base.timestamped_migrations 10 | Time.now.utc.strftime('%Y%m%d%H%M%S') 11 | else 12 | format('%.3d', (current_migration_number(dirname) + 1)) 13 | end 14 | end 15 | 16 | def manifest 17 | migration_template 'migration.rb', 18 | 'db/migrate/acts_as_commentable_upgrade_migration.rb' 19 | copy_file 'comment.rb', 'app/models/comment.rb' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_upgrade_migration/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | acts_as_nested_set scope: [:commentable_id, :commentable_type] 3 | 4 | validates :body, presence: true 5 | validates :user, presence: true 6 | 7 | # NOTE: install the acts_as_votable plugin if you 8 | # want user to vote on the quality of comments. 9 | # acts_as_votable 10 | 11 | belongs_to :commentable, polymorphic: true 12 | 13 | # NOTE: Comments belong to a user 14 | belongs_to :user 15 | 16 | # Helper class method that allows you to build a comment 17 | # by passing a commentable object, a user_id, and comment text 18 | # example in readme 19 | def self.build_from(obj, user_id, comment) 20 | new \ 21 | commentable: obj, 22 | body: comment, 23 | user_id: user_id 24 | end 25 | 26 | # helper method to check if a comment has children 27 | def has_children? 28 | children.size > 0 29 | end 30 | 31 | # Helper class method to lookup all comments assigned 32 | # to all commentable types for a given user. 33 | scope :find_comments_by_user, lambda { |user| 34 | where(user_id: user.id).order('created_at DESC') 35 | } 36 | 37 | # Helper class method to look up all comments for 38 | # commentable class name and commentable id. 39 | scope :find_comments_for_commentable, lambda { |type, id| 40 | where(commentable_type: type.to_s, 41 | commentable_id: id).order('created_at DESC') 42 | } 43 | 44 | # Helper class method to look up a commentable object 45 | # given the commentable class name and id 46 | def self.find_commentable(commentable_str, commentable_id) 47 | commentable_str.constantize.find(commentable_id) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_upgrade_migration/templates/migration.rb: -------------------------------------------------------------------------------- 1 | class ActsAsCommentableUpgradeMigration < ActiveRecord::Migration 2 | def self.up 3 | rename_column :comments, :comment, :body 4 | add_column :comments, :subject, :string 5 | add_column :comments, :parent_id, :integer 6 | add_column :comments, :lft, :integer 7 | add_column :comments, :rgt, :integer 8 | add_column :comments, :updated_at, :datetime 9 | 10 | add_index :comments, :commentable_id 11 | end 12 | 13 | def self.down 14 | rename_column :comments, :body, :comment 15 | remove_column :comments, :subject 16 | remove_column :comments, :parent_id 17 | remove_column :comments, :lft 18 | remove_column :comments, :rgt 19 | remove_column :comments, :updated_at 20 | 21 | remove_index :comments, :commentable_id 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_with_threading_migration/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | run rails generate acts_as_commentable_with_threading_migration 3 | 4 | no need to specify any parameters after acts_as_commentable_with_threading_migration as the plugin relies on the table being named "comments" 5 | the acts_as_commentable_with_threading_migration file will be created in db/migrate -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_with_threading_migration/acts_as_commentable_with_threading_migration_generator.rb: -------------------------------------------------------------------------------- 1 | class ActsAsCommentableWithThreadingMigrationGenerator < Rails::Generators::Base 2 | include Rails::Generators::Migration 3 | 4 | source_root File.expand_path('../templates', __FILE__) 5 | 6 | # Complete wtf that this isn't provided elsewhere. 7 | def self.next_migration_number(dirname) 8 | if ActiveRecord::Base.timestamped_migrations 9 | Time.now.utc.strftime('%Y%m%d%H%M%S') 10 | else 11 | format('%.3d', (current_migration_number(dirname) + 1)) 12 | end 13 | end 14 | 15 | def manifest 16 | migration_template 'migration.rb', 17 | 'db/migrate/acts_as_commentable_with_threading_migration.rb' 18 | copy_file 'comment.rb', 'app/models/comment.rb' 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_with_threading_migration/templates/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | acts_as_nested_set scope: [:commentable_id, :commentable_type] 3 | 4 | validates :body, presence: true 5 | validates :user, presence: true 6 | 7 | # NOTE: install the acts_as_votable plugin if you 8 | # want user to vote on the quality of comments. 9 | # acts_as_votable 10 | 11 | belongs_to :commentable, polymorphic: true 12 | 13 | # NOTE: Comments belong to a user 14 | belongs_to :user 15 | 16 | # Helper class method that allows you to build a comment 17 | # by passing a commentable object, a user_id, and comment text 18 | # example in readme 19 | def self.build_from(obj, user_id, comment) 20 | new \ 21 | commentable: obj, 22 | body: comment, 23 | user_id: user_id 24 | end 25 | 26 | # helper method to check if a comment has children 27 | def has_children? 28 | children.any? 29 | end 30 | 31 | # Helper class method to lookup all comments assigned 32 | # to all commentable types for a given user. 33 | scope :find_comments_by_user, lambda { |user| 34 | where(user_id: user.id).order('created_at DESC') 35 | } 36 | 37 | # Helper class method to look up all comments for 38 | # commentable class name and commentable id. 39 | scope :find_comments_for_commentable, lambda { |type, id| 40 | where(commentable_type: type.to_s, commentable_id: id) 41 | .order('created_at DESC') 42 | } 43 | 44 | # Helper class method to look up a commentable object 45 | # given the commentable class name and id 46 | def self.find_commentable(commentable_str, commentable_id) 47 | commentable_str.constantize.find(commentable_id) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/generators/acts_as_commentable_with_threading_migration/templates/migration.rb: -------------------------------------------------------------------------------- 1 | class ActsAsCommentableWithThreadingMigration < ActiveRecord::Migration 2 | def self.up 3 | create_table :comments, force: true do |t| 4 | t.integer :commentable_id 5 | t.string :commentable_type 6 | t.string :title 7 | t.text :body 8 | t.string :subject 9 | t.integer :user_id, null: false 10 | t.integer :parent_id, :lft, :rgt 11 | t.timestamps 12 | end 13 | 14 | add_index :comments, :user_id 15 | add_index :comments, [:commentable_id, :commentable_type] 16 | end 17 | 18 | def self.down 19 | drop_table :comments 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'acts_as_commentable_with_threading' 3 | -------------------------------------------------------------------------------- /spec/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('./spec_helper', File.dirname(__FILE__)) 2 | 3 | # Specs some of the behavior of awesome_nested_set although does so to 4 | # demonstrate the use of this gem 5 | describe Comment do 6 | before do 7 | @user = User.create! 8 | @comment = Comment.create!(body: 'Root comment', user: @user) 9 | end 10 | 11 | describe 'that is valid' do 12 | it 'should have a user' do 13 | expect(@comment.user).not_to be_nil 14 | end 15 | 16 | it 'should have a body' do 17 | expect(@comment.body).not_to be_nil 18 | end 19 | end 20 | 21 | it 'should not have a parent if it is a root Comment' do 22 | expect(@comment.parent).to be_nil 23 | end 24 | 25 | it 'can have see how child Comments it has' do 26 | expect(@comment.children.size).to eq(0) 27 | end 28 | 29 | it 'can add child Comments' do 30 | grandchild = Comment.new(body: 'This is a grandchild', user: @user) 31 | grandchild.save! 32 | grandchild.move_to_child_of(@comment) 33 | expect(@comment.children.size).to eq(1) 34 | end 35 | 36 | describe 'after having a child added' do 37 | before do 38 | @child = Comment.create!(body: 'Child comment', user: @user) 39 | @child.move_to_child_of(@comment) 40 | end 41 | 42 | it 'can be referenced by its child' do 43 | expect(@child.parent).to eq(@comment) 44 | end 45 | 46 | it 'can see its child' do 47 | expect(@comment.children.first).to eq(@child) 48 | end 49 | end 50 | 51 | describe 'finders' do 52 | describe '#find_comments_by_user' do 53 | before :each do 54 | @other_user = User.create! 55 | @user_comment = Comment.create!(body: 'Child comment', user: @user) 56 | @non_user_comment = Comment.create!(body: 'Child comment', 57 | user: @other_user) 58 | @comments = Comment.find_comments_by_user(@user) 59 | end 60 | 61 | it 'should return all the comments created by the passed user' do 62 | expect(@comments).to include(@user_comment) 63 | end 64 | 65 | it 'should not return comments created by non-passed users' do 66 | expect(@comments).not_to include(@non_user_comment) 67 | end 68 | end 69 | 70 | describe '#find_comments_for_commentable' do 71 | before :each do 72 | @other_user = User.create! 73 | @user_comment = 74 | Comment.create!(body: 'from user', 75 | commentable_type: @other_user.class.to_s, 76 | commentable_id: @other_user.id, 77 | user: @user) 78 | 79 | @other_comment = 80 | Comment.create!(body: 'from other user', 81 | commentable_type: @user.class.to_s, 82 | commentable_id: @user.id, 83 | user: @other_user) 84 | 85 | @comments = 86 | Comment.find_comments_for_commentable(@other_user.class, 87 | @other_user.id) 88 | end 89 | 90 | it 'should return the comments for the passed commentable' do 91 | expect(@comments).to include(@user_comment) 92 | end 93 | 94 | it 'should not return the comments for non-passed commentables' do 95 | expect(@comments).not_to include(@other_comment) 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /spec/commentable_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('./spec_helper', File.dirname(__FILE__)) 2 | 3 | describe 'A class that is commentable' do 4 | it 'can have many root comments' do 5 | expect(Commentable.new.comment_threads.respond_to?(:each)).to eq(true) 6 | end 7 | 8 | describe 'when is destroyed' do 9 | before :each do 10 | @user = User.create! 11 | @commentable = Commentable.create! 12 | @comment = Comment.create!(user: @user, 13 | commentable: @commentable, 14 | body: 'blargh') 15 | end 16 | 17 | it 'also destroys its root comments' do 18 | @commentable.destroy 19 | expect(Comment.all).not_to include(@comment) 20 | end 21 | 22 | it 'also destroys its nested comments' do 23 | child = Comment.new(body: 'This is a child', 24 | commentable: @commentable, 25 | user: @user) 26 | child.save! 27 | child.move_to_child_of(@comment) 28 | 29 | @commentable.destroy 30 | expect(Comment.all).not_to include(@comment) 31 | expect(Comment.all).not_to include(child) 32 | end 33 | end 34 | 35 | describe 'special class finders' do 36 | before :each do 37 | @user = User.create! 38 | @commentable = Commentable.create! 39 | @other_commentable = Commentable.create! 40 | end 41 | 42 | describe '#find_comments_for' do 43 | before :each do 44 | @comment = Comment.create!(user: @user, 45 | commentable: @commentable, 46 | body: 'blargh') 47 | 48 | @other_comment = Comment.create!(user: @user, 49 | commentable: @other_commentable, 50 | body: 'hello') 51 | 52 | @comments = Commentable.find_comments_for(@commentable) 53 | end 54 | 55 | it 'should return the comments for the passed commentable' do 56 | expect(@comments).to include(@comment) 57 | end 58 | 59 | it 'should not return the comments for other commentables' do 60 | expect(@comments).not_to include(@other_comment) 61 | end 62 | end 63 | 64 | describe '#find_comments_by_user' do 65 | before :each do 66 | @user2 = User.create! 67 | 68 | @comment = Comment.create!(user: @user, 69 | commentable: @commentable, 70 | body: 'blargh') 71 | 72 | @other_comment = Comment.create!(user: @user2, 73 | commentable: @other_commentable, 74 | body: 'hello') 75 | 76 | @comments = Commentable.find_comments_by_user(@user) 77 | end 78 | 79 | it 'should return comments by the passed user' do 80 | expect(@comments.all? { |c| c.user == @user }).to eq(true) 81 | end 82 | 83 | it 'should not return comments by other users' do 84 | expect(@comments.any? { |c| c.user != @user }).to eq(false) 85 | end 86 | end 87 | end 88 | 89 | describe 'instance methods' do 90 | describe '#comments_ordered_by_submitted' do 91 | before :each do 92 | @user = User.create! 93 | @commentable = Commentable.create! 94 | @other_commentable = Commentable.create! 95 | @comment = Comment.create!(user: @user, 96 | commentable: @commentable, 97 | body: 'sup') 98 | @older_comment = Comment.create!(user: @user, 99 | commentable: @commentable, 100 | body: 'sup', 101 | created_at: 1.week.ago) 102 | @oldest_comment = Comment.create!(user: @user, 103 | commentable: @commentable, 104 | body: 'sup', 105 | created_at: 2.years.ago) 106 | @other_comment = Comment.create!(user: @user, 107 | commentable: @other_commentable, 108 | body: 'sup') 109 | @comments = @commentable.comments_ordered_by_submitted 110 | end 111 | 112 | it 'should return its own comments, ordered with the newest first' do 113 | expect(@comments.all? do |c| 114 | c.commentable_type == @commentable.class.to_s && 115 | c.commentable_id == @commentable.id 116 | end).to eq(true) 117 | @comments.each_cons(2) do |c, c2| 118 | expect(c.created_at).to be > c2.created_at 119 | end 120 | end 121 | 122 | it 'should not include comments for other commentables' do 123 | expect(@comments.any? do |c| 124 | c.commentable_type != @commentable.class.to_s || 125 | c.commentable_id != @commentable.id 126 | end).to eq(false) 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/db/database.yml: -------------------------------------------------------------------------------- 1 | sqlite3: 2 | adapter: sqlite3 3 | database: acts_as_commentable_with_threading.sqlite3.db 4 | sqlite3mem: 5 | :adapter: sqlite3 6 | :database: ":memory:" 7 | postgresql: 8 | :adapter: postgresql 9 | :username: postgres 10 | :password: postgres 11 | :database: acts_as_commentable_with_threading_plugin_test 12 | :min_messages: ERROR 13 | mysql: 14 | :adapter: mysql 15 | :host: localhost 16 | :username: root 17 | :password: 18 | :database: acts_as_commentable_with_threading_plugin_test 19 | -------------------------------------------------------------------------------- /spec/db/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define(version: 1) do 2 | create_table 'users', force: true, &:timestamps 3 | 4 | create_table 'commentables', force: true, &:timestamps 5 | 6 | create_table 'comments', force: true do |t| 7 | t.integer 'commentable_id' 8 | t.string 'commentable_type', limit: 15, default: '' 9 | t.string 'title', default: '' 10 | t.text 'body', default: '' 11 | t.string 'subject', default: '' 12 | t.integer 'user_id', null: false 13 | t.integer 'parent_id' 14 | t.integer 'lft' 15 | t.integer 'rgt' 16 | t.timestamps 17 | end 18 | 19 | add_index 'comments', 'user_id' 20 | add_index 'comments', 'commentable_id' 21 | end 22 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | require 'logger' 4 | 5 | plugin_test_dir = File.dirname(__FILE__) 6 | 7 | ActiveRecord::Base.logger = Logger.new(File.join(plugin_test_dir, 'debug.log')) 8 | 9 | ActiveRecord::Base.configurations = 10 | YAML.load_file(File.join(plugin_test_dir, 'db', 'database.yml')) 11 | ActiveRecord::Base.establish_connection((ENV['DB'] || 'sqlite3mem').to_sym) 12 | ActiveRecord::Migration.verbose = false 13 | load(File.join(plugin_test_dir, 'db', 'schema.rb')) 14 | 15 | require File.join(plugin_test_dir, '..', 'init') 16 | require File.join(plugin_test_dir, '..', 'lib', 'generators', 17 | 'acts_as_commentable_with_threading_migration', 18 | 'templates', 'comment') 19 | 20 | class User < ActiveRecord::Base 21 | has_many :comments 22 | end 23 | 24 | class Commentable < ActiveRecord::Base 25 | acts_as_commentable 26 | end 27 | 28 | ActiveRecord::Base.send(:include, Acts::CommentableWithThreading) 29 | --------------------------------------------------------------------------------