can_flag 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Cosmin Radoi
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.
data/README ADDED
@@ -0,0 +1,62 @@
1
+ Can Flag
2
+ =================
3
+ Strongly inspired by acts_as_flaggable.
4
+ Intended to allow your users to flag content as inappropriate, and set up some ways for you
5
+ to deal with the content.
6
+
7
+ == Resources
8
+
9
+ Install
10
+
11
+ 1. Download the tarball from github, or, clone the repository and symlink it into your application.
12
+ Yes, I'm serious.
13
+
14
+ 2. $ script/generate can_flag flags
15
+
16
+ This will create the following files:
17
+ app/controllers/flags_controller.rb
18
+ spec/helpers/flags_controller_spec.rb
19
+ app/helpers/flags_helper
20
+ db/migrate/flags_migration.rb
21
+
22
+ 3. Modify any content model with
23
+
24
+ class Article < ActiveRecord::Base
25
+ can_be_flagged
26
+ end
27
+
28
+ You can also add a callback
29
+
30
+ class Article
31
+ can_be_flagged
32
+ def after_flagged
33
+ # send an email
34
+ # delete this post
35
+ # suspend the owner of the post if the flags.size > 3
36
+ # suspend the owner of the post if their total flags count > 5
37
+ end
38
+ end
39
+
40
+ 4. Modify your user model with
41
+
42
+ class User < ActiveRecord::Base
43
+ can_flag
44
+ end
45
+
46
+ 5. Usage
47
+
48
+ article = Article.find(4)
49
+ current_user.flags.create :flaggable => article
50
+
51
+
52
+
53
+ * When a flag is added via add_flag, flagged(flag, flag_count) is called
54
+ on the flaggable model. This allows the model to perform certain
55
+ operations if the number of flags reaches a certain point. For example,
56
+ you may want to mark a Post as deleted if a Post receives too many "spam"
57
+ flags
58
+
59
+ * Each flag reference flaggable object
60
+
61
+ model = Model.find(1)
62
+ model.flags.get(0).commtable == model
@@ -0,0 +1,140 @@
1
+ # CanFlag
2
+ module Caboose
3
+ module Can
4
+ module Flag
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ if defined?(::ActiveSupport::Callbacks)
8
+ base.define_callbacks :after_flagged
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # Call can_be_flagged from your content model, or from anything
14
+ # you want to flag.
15
+ def can_be_flagged(opts={})
16
+ has_many :flags, :as => :flaggable, :dependent => :destroy
17
+ validates_associated :flags, :message => 'failed to validate'
18
+ include Caboose::Can::Flag::InstanceMethods
19
+ extend Caboose::Can::Flag::SingletonMethods
20
+ cattr_accessor :reasons
21
+ self.reasons = opts[:reasons] || [:inappropriate]
22
+ (::Flag.flaggable_models ||= []) << self
23
+ end
24
+
25
+ # Call can_flag from your user model, or anything that can own a flagging.
26
+ # That's a paddlin!
27
+ # Limitation for now: you should only allow one model to own flags.
28
+ # This is ridiculously easy to make polymorphic, but no ponies yet.
29
+ def can_flag
30
+ # has_many :flaggables, :foreign_key => "user_id"
31
+ # User created these flags
32
+ has_many :flags, :foreign_key => "user_id", :order => "id desc"
33
+
34
+ # User was responsible for creating this filth
35
+ has_many :flaggings, :foreign_key => "flaggable_user_id", :class_name => "Flag"
36
+ include CanFlagInstanceMethods
37
+
38
+ # Associate the flag back here
39
+ # Flag.belongs_to :user
40
+ # Flag.belongs_to :owner, :foreign_key => flaggable_user_id
41
+ ::Flag.class_eval "belongs_to :#{name.underscore}, :foreign_key => :user_id;
42
+ belongs_to :owner, :foreign_key => :flaggable_user_id, :class_name => '#{name}'"
43
+ end
44
+ end
45
+
46
+ # This module contains class methods
47
+ module SingletonMethods
48
+ # # Helper method to lookup for flags for a given object.
49
+ # # This method is equivalent to obj.flags.
50
+ # def find_flags_for(obj)
51
+ # flaggable = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
52
+ #
53
+ # Flag.find(:all,
54
+ # :conditions => ["flaggable_id = ? and flaggable_type = ?", obj.id, flaggable],
55
+ # :order => "created_at DESC"
56
+ # )
57
+ # end
58
+ #
59
+ # # Helper class method to lookup flags for
60
+ # # the mixin flaggable type written by a given user.
61
+ # # This method is NOT equivalent to Flag.find_flags_for_user
62
+ # def find_flags_by_user(user)
63
+ # flaggable = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
64
+ #
65
+ # Flag.find(:all,
66
+ # :conditions => ["user_id = ? and flaggable_type = ?", user.id, flaggable],
67
+ # :order => "created_at DESC"
68
+ # )
69
+ # end
70
+ end
71
+
72
+ module CanFlagInstanceMethods
73
+ def flagged?(content)
74
+ logger.warn "Looking for flags with #{content.inspect} #{content.class.name}"
75
+ ::Flag.find(:first,
76
+ :conditions => { :flaggable_type => content.class.name, :flaggable_id => content[:id] })
77
+ end
78
+
79
+ def flagged_by?(content, user)
80
+ ::Flag.find(:first,
81
+ :conditions => { :flaggable_type => content.class.name, :flaggable_id => content[:id], :flaggable_user_id => user.id })
82
+ end
83
+ end
84
+
85
+ ## This module contains instance methods
86
+ module InstanceMethods
87
+
88
+ def flagged?
89
+ flags.size > 0
90
+ end
91
+
92
+ # Flag content with a mass-updater; sets the flagging user.
93
+ # article.update_attributes { 'flagged' => current_user.id }
94
+ def flagged=(by_who)
95
+ flags.build :user_id => by_who
96
+ end
97
+ #
98
+ # # Check to see if the passed in user has flagged this object before.
99
+ # # Optionally you can test to see if this user has flagged this object
100
+ # # with a specific flag
101
+ # def user_has_flagged?(user, flag = nil)
102
+ # conditions = flag.nil? ? {} : {:flag => flag}
103
+ # conditions.merge!({:user_id => user.id})
104
+ # return flags.count(:conditions =>conditions) > 0
105
+ # end
106
+ #
107
+ # # Count the number of flags tha have this specific
108
+ # # flag set
109
+ # def count_flags_with_flag(flag)
110
+ # flags.count(:conditions => ["flag = ?", flag])
111
+ # end
112
+ #
113
+ # # Add a flag. You can either pass in an
114
+ # # instance of a flag or pass in a hash of attributes to be used to
115
+ # # instantiate a new flag object
116
+ # def add_flag(options)
117
+ # if options.kind_of?(Hash)
118
+ # flag = Flag.new(options)
119
+ # elsif options.kind_of?(Flag)
120
+ # flag = options
121
+ # else
122
+ # raise "Invalid options"
123
+ # end
124
+ #
125
+ # flags << flag
126
+ #
127
+ # # Call flagged to allow model to handle the act of being
128
+ # # flagged
129
+ # flagged(flag.flag, count_flags_with_flag(flag.flag))
130
+ # end
131
+ #
132
+ # # Meant to be overriden
133
+ # protected
134
+ # def flagged(flag, flag_count)
135
+ # end
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,30 @@
1
+ # Stolen from Technoweenie's restful_authentication
2
+ Rails::Generator::Commands::Create.class_eval do
3
+ def route_resource(*resources)
4
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
5
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
6
+
7
+ logger.route "map.resource #{resource_list}"
8
+ unless options[:pretend]
9
+ gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
10
+ "#{match}\n map.resource #{resource_list}\n"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ Rails::Generator::Commands::Destroy.class_eval do
17
+ def route_resource(*resources)
18
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
19
+ look_for = "\n map.resource #{resource_list}\n"
20
+ logger.route "map.resource #{resource_list}"
21
+ gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
22
+ end
23
+ end
24
+
25
+ Rails::Generator::Commands::List.class_eval do
26
+ def route_resource(*resources)
27
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
28
+ logger.route "map.resource #{resource_list}"
29
+ end
30
+ end
@@ -0,0 +1,65 @@
1
+ class Flag < ActiveRecord::Base
2
+ # serialize :flag, Symbol
3
+ belongs_to :flaggable, :polymorphic => true
4
+
5
+ # This will contain the names of all models that can_be_flagged
6
+ cattr_accessor :flaggable_models
7
+
8
+ # This line is dynamically generated when you call "can_flag" in your user/account model.
9
+ # It assumes that content is owned by the same class as flaggers.
10
+ # belongs_to :owner, :through => :flaggable, :class_name => ??
11
+
12
+ # This is set dynamically in the plugin.
13
+ # define "can_flag" in your user/account model.
14
+ # belongs_to :user
15
+
16
+ validates_presence_of :flaggable_id, :flaggable_type
17
+
18
+ # requires all your content to have a user_id. if not, then
19
+ validates_presence_of :flaggable_user_id, :on => :create,
20
+ # :message => "error - your content must be owned by a user.",
21
+ :if => Proc.new { |c| c.flaggable and c.flaggable.user_id }
22
+
23
+ # A user can flag a specific flaggable with a specific flag once
24
+ validates_uniqueness_of :user_id, :scope => [:flaggable_id, :flaggable_type]
25
+
26
+ after_create :callback_flaggable
27
+ # Pings the 'after_flagged' callback in the content model, if it exists.
28
+ def callback_flaggable
29
+ flaggable.callback :after_flagged
30
+ end
31
+
32
+ before_validation_on_create :set_owner_id
33
+ def set_owner_id
34
+ self.flaggable_user_id = flaggable.user_id
35
+ end
36
+
37
+ validates_each :reason do |record,attr,value|
38
+ record.errors.add(attr, "don't include '#{value}' as an option") if value and !record.flaggable.reasons.include?(value.to_sym)
39
+ end
40
+
41
+ # UNTESTED
42
+ # # Helper class method to lookup all flags assigned
43
+ # # to all flaggable types for a given user.
44
+ # def self.find_flags_by_user(user)
45
+ # find(:all,
46
+ # :conditions => ["user_id = ?", user.id],
47
+ # :order => "created_at DESC"
48
+ # )
49
+ # end
50
+ #
51
+ # # Helper class method to look up all flags for
52
+ # # flaggable class name and flaggable id.
53
+ # def self.find_flags_for_flaggable(flaggable_str, flaggable_id)
54
+ # find(:all,
55
+ # :conditions => ["flaggable_type = ? and flaggable_id = ?", flaggable_str, flaggable_id],
56
+ # :order => "created_at DESC"
57
+ # )
58
+ # end
59
+ #
60
+ # # Helper class method to look up a flaggable object
61
+ # # given the flaggable class name and id
62
+ # def self.find_flaggable(flaggable_str, flaggable_id)
63
+ # flaggable_str.constantize.find(flaggable_id)
64
+ # end
65
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class Article < ActiveRecord::Base
4
+ end
5
+ class Content < ActiveRecord::Base
6
+ set_table_name 'articles' # cheater
7
+ end
8
+
9
+ class CanBeFlaggedOptionsTest < Test::Unit::TestCase
10
+ def setup
11
+ Article.class_eval do
12
+ can_be_flagged :reasons => [:porn, :troll]
13
+ end
14
+ end
15
+
16
+ def test_sets_options_as_class_var
17
+ assert_equal [:porn, :troll], Article.reasons
18
+ end
19
+
20
+ def test_does_not_leak_reasons_betwixt_classes
21
+ Content.class_eval do
22
+ can_be_flagged :reasons => [:spam]
23
+ end
24
+ assert_equal [:porn, :troll], Article.reasons
25
+ assert_equal [:spam], Content.reasons
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class Article < ActiveRecord::Base
4
+ end
5
+
6
+ class ContentAssociationTest < Test::Unit::TestCase
7
+
8
+ def test_article_can_has_flag
9
+ Article.class_eval {
10
+ can_be_flagged
11
+ }
12
+ assert_not_nil Article.reflect_on_association(:flags)
13
+ assert_equal :has_many, Article.reflect_on_association(:flags).macro,
14
+ "Should create the 'flags' association"
15
+ #assert_not_nil Article.reflect_on_association(:flaggable)
16
+ #assert_equal :has_many, Article.reflect_on_association(:flaggable).macro,
17
+ # "Should create the 'flaggable' association"
18
+ end
19
+
20
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class Article < ActiveRecord::Base
4
+ can_be_flagged
5
+ end
6
+
7
+ class ContentAttributesTest < Test::Unit::TestCase
8
+ def test_article_cab_has_attributes_like_states
9
+ a = Article.new
10
+ assert a.respond_to?(:flagged?), "Should have the 'flagged?' method defined"
11
+ assert a.respond_to?(:flagged=), "Should have the 'flagged=' writer defined"
12
+ end
13
+
14
+ def test_article_can_has_writer
15
+ a = Article.new
16
+ assert ! a.flagged?, "Article should not be flagged by default"
17
+ a.attributes = { 'flagged' => true }
18
+ assert a.flagged?
19
+ end
20
+
21
+ end
@@ -0,0 +1,18 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: can_flag_plugin.sqlite.db
4
+ sqlite3:
5
+ :adapter: sqlite3
6
+ :dbfile: can_flag_plugin.sqlite3.db
7
+ postgresql:
8
+ :adapter: postgresql
9
+ :username: postgres
10
+ :password: postgres
11
+ :database: can_flag_plugin_test
12
+ :min_messages: ERROR
13
+ mysql:
14
+ :adapter: mysql
15
+ :host: localhost
16
+ :username: rails
17
+ :password:
18
+ :database: can_flag_plugin_test
@@ -0,0 +1,36 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class Article < ActiveRecord::Base
4
+ can_be_flagged
5
+ attr_accessor :called_back
6
+
7
+ def after_flagged
8
+ @called_back = true
9
+ end
10
+ end
11
+
12
+ class FlagTest < Test::Unit::TestCase
13
+
14
+ def test_calls_back_to_article
15
+ article = Article.create :title => "My article", :body => "Five five five", :user_id => 1
16
+ assert ! article.called_back
17
+ flag = Flag.create! :flaggable => article
18
+ assert article.called_back, "Article should call 'after_flagged' callback"
19
+ end
20
+
21
+ def test_sets_flaggable_user_id
22
+ article = Article.create :title => "My article", :body => "Five five five", :user_id => 1
23
+ flag = Flag.create :flaggable => article
24
+ flag.save!
25
+ assert_equal 1, flag.flaggable_user_id
26
+ end
27
+
28
+ def test_only_allows_valid_reasons
29
+ Article.class_eval { can_be_flagged :reasons => [ :foolish ] }
30
+ article = Article.create :title => "My article", :body => "Five five five", :user_id => 1
31
+ flag = Flag.create :flaggable => article, :reason => "foolish"
32
+ assert flag.save!
33
+ flag.reason = "Monkeys"
34
+ assert ! flag.save
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+
3
+ create_table :articles do |t|
4
+ t.string :title
5
+ t.string :body
6
+ t.string :user_id
7
+ end
8
+
9
+ create_table :users do |t|
10
+ t.string :login
11
+ end
12
+
13
+ create_table :flags do |t|
14
+ t.integer :user_id
15
+ t.integer :flaggable_id
16
+ t.string :flaggable_type
17
+ t.integer :flaggable_user_id
18
+ t.string :reason
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ ENV['RAILS_ENV'] = 'test'
4
+ ENV['RAILS_ROOT'] ||= '~/dev/oss/altered_beast/' #File.dirname(__FILE__) + '/../../../..'
5
+
6
+ require 'test/unit'
7
+ require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
8
+ require 'active_record/fixtures'
9
+ require 'action_controller/test_process'
10
+
11
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
12
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
13
+
14
+ # Cargo culted from attachment_fu!
15
+ db_adapter = ENV['DB']
16
+
17
+ # no db passed, try one of these fine config-free DBs before bombing.
18
+ db_adapter ||=
19
+ begin
20
+ require 'rubygems'
21
+ require 'sqlite'
22
+ 'sqlite'
23
+ rescue MissingSourceFile
24
+ begin
25
+ require 'sqlite3'
26
+ 'sqlite3'
27
+ rescue MissingSourceFile
28
+ end
29
+ end
30
+
31
+ if db_adapter.nil?
32
+ raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
33
+ end
34
+
35
+ ActiveRecord::Base.establish_connection(config[db_adapter])
36
+
37
+ load(File.dirname(__FILE__) + "/schema.rb")
38
+
39
+ # Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures"
40
+ # $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
@@ -0,0 +1,32 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class User<ActiveRecord::Base; end
4
+
5
+ class UserAssociationTest < Test::Unit::TestCase
6
+ def test_creates_user_association_in_flag
7
+ assert_nil Flag.reflect_on_association(:user)
8
+ User.class_eval do
9
+ can_flag
10
+ end
11
+ assert_not_nil Flag.reflect_on_association(:user)
12
+ end
13
+ end
14
+
15
+ class User2<ActiveRecord::Base; set_table_name :users; end
16
+
17
+ class UserFlagAssociationTest < Test::Unit::TestCase
18
+
19
+ def test_creates_flaggable_associations_in_user
20
+ assert_nil User2.reflect_on_association(:flaggable)
21
+ User2.class_eval do
22
+ can_flag
23
+ end
24
+ assert_not_nil User2.reflect_on_association(:flaggings)
25
+ assert_equal :has_many, User2.reflect_on_association(:flaggings).macro
26
+ assert_nothing_raised { User2.new.flaggings }
27
+ assert_not_nil User2.reflect_on_association(:flags)
28
+ assert_equal :has_many, User2.reflect_on_association(:flags).macro
29
+ assert_nothing_raised { User2.new.flags }
30
+ end
31
+
32
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: can_flag
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ version: "1.0"
10
+ platform: ruby
11
+ authors:
12
+ - Courtenay Gasking
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-09 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 15
29
+ segments:
30
+ - 2
31
+ - 0
32
+ - 0
33
+ version: 2.0.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: activerecord
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 15
45
+ segments:
46
+ - 2
47
+ - 0
48
+ - 0
49
+ version: 2.0.0
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description:
53
+ email:
54
+ - courtenay@entp.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - MIT-LICENSE
61
+ files:
62
+ - lib/can_flag/rails_commands.rb
63
+ - lib/can_flag.rb
64
+ - lib/flag.rb
65
+ - test/can_be_flagged_options_test.rb
66
+ - test/content_association_test.rb
67
+ - test/content_attributes_test.rb
68
+ - test/database.yml
69
+ - test/flag_test.rb
70
+ - test/schema.rb
71
+ - test/test_helper.rb
72
+ - test/user_association_test.rb
73
+ - MIT-LICENSE
74
+ - README
75
+ has_rdoc: true
76
+ homepage: http://github.com/courtenay/can_flag
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --main
82
+ - README.md
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project:
106
+ rubygems_version: 1.3.7
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Rails plugin to allow users to flag content for review
110
+ test_files:
111
+ - test/can_be_flagged_options_test.rb
112
+ - test/content_association_test.rb
113
+ - test/content_attributes_test.rb
114
+ - test/flag_test.rb
115
+ - test/schema.rb
116
+ - test/test_helper.rb
117
+ - test/user_association_test.rb