├── .gitignore
├── .ruby-gemset.example
├── .ruby-version.example
├── .rvmrc.example
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── app
├── assets
│ ├── javascripts
│ │ ├── the_comments.js.coffee
│ │ └── the_comments_manage.js.coffee
│ └── stylesheets
│ │ └── the_comments.css.scss
├── controllers
│ ├── _templates_
│ │ └── comments_controller.rb
│ └── concerns
│ │ └── the_comments
│ │ ├── controller.rb
│ │ └── view_token.rb
├── helpers
│ └── render_comments_tree_helper.rb
├── models
│ ├── _templates_
│ │ └── comment.rb
│ └── concerns
│ │ └── the_comments
│ │ ├── comment.rb
│ │ ├── comment_states.rb
│ │ ├── commentable.rb
│ │ └── user.rb
└── views
│ └── the_comments
│ ├── _tree.html.erb
│ ├── haml
│ ├── _additional_info.html.haml
│ ├── _comment.html.haml
│ ├── _comment_body.html.haml
│ ├── _comment_edit.html.haml
│ ├── _form.html.haml
│ ├── _guest_form.html.haml
│ ├── _logined_form.html.haml
│ ├── _manage_controls.html.haml
│ ├── _sidebar.html.haml
│ ├── _sidebar_admin.html.haml
│ ├── _sidebar_backlink.html.haml
│ ├── _sidebar_user.html.haml
│ ├── _tree.html.haml
│ └── manage.html.haml
│ └── slim
│ ├── _additional_info.html.slim
│ ├── _comment.html.slim
│ ├── _comment_body.html.slim
│ ├── _comment_edit.html.slim
│ ├── _form.html.slim
│ ├── _guest_form.html.slim
│ ├── _logined_form.html.slim
│ ├── _manage_controls.html.slim
│ ├── _sidebar.html.slim
│ ├── _sidebar_admin.html.slim
│ ├── _sidebar_backlink.html.slim
│ ├── _sidebar_user.html.slim
│ ├── _tree.html.slim
│ ├── index.html.slim
│ ├── manage.html.slim
│ └── my_comments.html.slim
├── config
├── initializers
│ └── the_comments.rb
├── locales
│ ├── en.yml
│ └── ru.yml
└── routes.rb
├── db
└── migrate
│ ├── 20130101010101_the_comments_change_user.rb
│ ├── 20130101010102_the_comments_create_comments.rb
│ └── 20130101010103_the_comments_change_commentable.rb
├── docs
├── admin_ui_installation.md
├── advanced_installation.md
├── comment_api.md
├── commentable_api.md
├── config_file.md
├── content_preprocessors.md
├── customazation_of_views.md
├── denormalization_and_recent_comments.md
├── documentation.md
├── generators.md
├── pagination.md
├── routes.md
├── screencast.jpg
├── the_comments.jpg
├── the_comments_view_1.gif
├── the_comments_view_2.gif
├── the_comments_view_3.gif
├── the_comments_view_4.gif
├── the_comments_view_5.gif
├── user_api.md
├── what_is_comcoms.md
├── whats_wrong_with_other_gems.md
└── where_is_example_application.md
├── gem_version.rb
├── lib
├── generators
│ └── the_comments
│ │ ├── USAGE
│ │ ├── the_comments_generator.rb
│ │ └── views_generator.rb
├── the_comments.rb
└── the_comments
│ ├── config.rb
│ └── version.rb
├── spec
└── dummy_app
│ ├── .gitignore
│ ├── .rspec
│ ├── Gemfile
│ ├── README.md
│ ├── Rakefile
│ ├── app
│ ├── assets
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ ├── admin_panel.js
│ │ │ └── application.js
│ │ └── stylesheets
│ │ │ ├── admin_panel.css
│ │ │ ├── app.css.scss
│ │ │ └── application.css
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── comments_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── posts_controller.rb
│ │ └── users_controller.rb
│ ├── helpers
│ │ └── application_helper.rb
│ ├── mailers
│ │ └── .keep
│ ├── models
│ │ ├── .keep
│ │ ├── comment.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── post.rb
│ │ └── user.rb
│ └── views
│ │ ├── layouts
│ │ ├── admin.html.haml
│ │ └── application.html.haml
│ │ └── posts
│ │ ├── index.html.haml
│ │ └── show.html.haml
│ ├── bin
│ ├── bundle
│ ├── rails
│ └── rake
│ ├── config.ru
│ ├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── backtrace_silencers.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── secret_token.rb
│ │ ├── session_store.rb
│ │ ├── sorcery.rb
│ │ ├── the_comments.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ └── routes.rb
│ ├── db
│ ├── migrate
│ │ ├── 20130712061503_sorcery_core.rb
│ │ ├── 20130712065951_create_posts.rb
│ │ ├── 20131027185332_change_user.the_comments_engine.rb
│ │ ├── 20131027185333_create_comments.the_comments_engine.rb
│ │ └── 20131027185334_change_commentable.the_comments_engine.rb
│ ├── schema.rb
│ └── seeds.rb
│ ├── lib
│ ├── assets
│ │ └── .keep
│ └── tasks
│ │ ├── .keep
│ │ └── app_bootstrap.rake
│ ├── log
│ └── .keep
│ ├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── favicon.ico
│ └── robots.txt
│ ├── spec
│ ├── factories
│ │ ├── post.rb
│ │ └── user.rb
│ ├── models
│ │ └── user_counters_spec.rb
│ └── spec_helper.rb
│ ├── test
│ ├── controllers
│ │ └── .keep
│ ├── fixtures
│ │ └── .keep
│ ├── helpers
│ │ └── .keep
│ ├── integration
│ │ └── .keep
│ ├── mailers
│ │ └── .keep
│ ├── models
│ │ └── .keep
│ └── test_helper.rb
│ └── vendor
│ └── assets
│ ├── javascripts
│ └── .keep
│ └── stylesheets
│ └── .keep
├── the_comments.gemspec
├── the_comments.yml.teamocil.example
└── views_converter.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 |
4 | .bundle
5 | .config
6 | .yardoc
7 |
8 | .rvmrc
9 | .ruby-gemset
10 | .ruby-version
11 |
12 | _yardoc
13 | coverage
14 | Gemfile.lock
15 | InstalledFiles
16 | lib/bundler/man
17 | spec/dummy_app/public/assets
18 |
19 | tmp
20 | doc
21 | pkg
22 | rdoc
23 |
24 | test/tmp
25 | spec/reports
26 | test/version_tmp
27 |
28 | .DS_Store
29 | .LSOverride
30 | .AppleDouble
--------------------------------------------------------------------------------
/.ruby-gemset.example:
--------------------------------------------------------------------------------
1 | the_comments
2 |
--------------------------------------------------------------------------------
/.ruby-version.example:
--------------------------------------------------------------------------------
1 | ruby-2.0.0-p353
2 |
--------------------------------------------------------------------------------
/.rvmrc.example:
--------------------------------------------------------------------------------
1 | rvm use ruby-2.0.0-p353@the_comments --create
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.3
4 | gemfile: spec/dummy_app/Gemfile
5 | script: "cd spec/dummy_app && rake db:bootstrap RAILS_ENV=test && rspec --format documentation"
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in the_comments.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Ilya N. Zykin
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TheComments
2 |
3 | [](http://badge.fury.io/rb/the_comments) | [](https://travis-ci.org/the-teacher/the_comments) | [](https://codeclimate.com/github/the-teacher/the_comments) | [(rubygems)](http://rubygems.org/gems/the_comments)
4 |
5 | TheComments - The best Rails gem for blog-style comments
6 |
7 | :question: [Why is TheComments better than other gems?](docs/whats_wrong_with_other_gems.md#why-thecomments-is-better-than-others-gems)
8 |
9 | ### Features
10 |
11 | * Threaded comments
12 | * Useful cache counters
13 | * Admin UI for moderation
14 | * Mountable Engine.routes
15 | * Online Support via skype: **ilya.killich**
16 | * [Denormalization](docs/denormalization_and_recent_comments.md) for recent comments
17 | * Production-ready commenting system for Rails 4+
18 | * Designed for preprocessors such as Sanitize, Textile, Markdown etc.
19 |
20 | ### :books: [Documentation](docs/documentation.md)
21 |
22 | ## If you have any questions
23 |
24 | Please try playing around with the **[Dummy App](spec/dummy_app)** in the `spec` folder first. An example integration is often better than any documentation! Thanks.
25 |
26 | ## How to start the dummy app (screencast)
27 |
28 | [](http://vk.com/video_ext.php?oid=49225742&id=166578209&hash=10be1dba625149bb&hd=3)
29 |
30 | ## Quick Start Installation
31 |
32 | **NB: In the following examples, `Posts` is the model to which comments are being added. For your app, the model might be `Articles` or similar instead.**
33 |
34 | ### 1. Install Gems
35 |
36 | **Gemfile**
37 |
38 | ```ruby
39 | gem "the_comments", "~> 2.2.1"
40 |
41 | gem 'haml' # or gem 'slim'
42 | gem 'awesome_nested_set' # or same gem
43 | ```
44 |
45 | **Bundle**
46 |
47 | ```
48 | bundle
49 | ```
50 |
51 | Don't forget to restart your server!
52 |
53 | ### 2. Add migrations
54 |
55 | ```
56 | rake the_comments_engine:install:migrations
57 | ```
58 |
59 | Will create:
60 |
61 | * xxxxx_change_user.rb
62 | * xxxxx_create_comments.rb
63 | * xxxxx_change_commentable.rb
64 |
65 | :warning: **Open and change xxxxx_change_commentable.rb migration**
66 |
67 | ```ruby
68 | class ChangeCommentable < ActiveRecord::Migration
69 | def change
70 | # Additional fields to Commentable Models
71 | # [:posts, :articles, ... ]
72 |
73 | # There is only Post model is commentable
74 | [:posts].each do |table_name|
75 | change_table table_name do |t|
76 | t.integer :draft_comments_count, default: 0
77 | t.integer :published_comments_count, default: 0
78 | t.integer :deleted_comments_count, default: 0
79 | end
80 | end
81 | end
82 | end
83 | ```
84 |
85 | :warning: **Open and change xxxxx_change_user.rb migration**
86 |
87 | ```ruby
88 | class TheCommentsChangeUser < ActiveRecord::Migration
89 | def change
90 | #if you User class is not called User, you may want to change it.
91 | change_table :users do |t|
92 | # "Written by me" (cache counters)
93 | t.integer :my_draft_comments_count, default: 0
94 | t.integer :my_published_comments_count, default: 0
95 | t.integer :my_comments_count, default: 0 # my_draft_comments_count + my_published_comments_count
96 |
97 | # commentable's comments => comcoms (cache counters)
98 | # Relation through Comment#holder_id field
99 | t.integer :draft_comcoms_count, default: 0
100 | t.integer :published_comcoms_count, default: 0
101 | t.integer :deleted_comcoms_count, default: 0
102 | t.integer :spam_comcoms_count, default: 0
103 | end
104 | end
105 | end
106 | ```
107 |
108 | **Invoke migrations**
109 |
110 | ```
111 | rake db:migrate
112 | ```
113 |
114 | ### 3. Code installation
115 |
116 | ```ruby
117 | rails g the_comments install
118 | ```
119 |
120 | Will create:
121 |
122 | * config/initializers/the_comments.rb
123 | * app/controllers/comments_controller.rb
124 | * app/models/comment.rb
125 |
126 | :warning: **Open each file and follow the instructions**
127 |
128 | ### 4. Models modifictions
129 |
130 | **app/models/user.rb**
131 |
132 | ```ruby
133 | class User < ActiveRecord::Base
134 | include TheComments::User
135 |
136 | has_many :posts
137 |
138 | # IT'S JUST AN EXAMPLE OF ANY ROLE SYSTEM
139 | def admin?
140 | self == User.first
141 | end
142 |
143 | # YOU HAVE TO IMPLEMENT YOUR ROLE POLICY FOR COMMENTS HERE
144 | def comments_admin?
145 | admin?
146 | end
147 |
148 | def comments_moderator? comment
149 | id == comment.holder_id
150 | end
151 | end
152 | ```
153 |
154 | **app/models/post.rb**
155 |
156 | ```ruby
157 | class Post < ActiveRecord::Base
158 | include TheComments::Commentable
159 |
160 | belongs_to :user
161 |
162 | # Denormalization methods
163 | # Check the documentation for information on advanced usage
164 | def commentable_title
165 | "Undefined Post Title"
166 | end
167 |
168 | def commentable_url
169 | "#"
170 | end
171 |
172 | def commentable_state
173 | "published"
174 | end
175 | end
176 | ```
177 |
178 | ### 5. Add routes
179 |
180 | **config/routes.rb**
181 |
182 | ```ruby
183 | MyApp::Application.routes.draw do
184 | root 'posts#index'
185 | resources :posts
186 |
187 | # ...
188 |
189 | # TheComments routes
190 | concern :user_comments, TheComments::UserRoutes.new
191 | concern :admin_comments, TheComments::AdminRoutes.new
192 | resources :comments, concerns: [:user_comments, :admin_comments]
193 | end
194 | ```
195 |
196 | Refer to the [documentation](docs/documentation.md) for more information
197 |
198 | ### 6. Add to Application Controller
199 |
200 | **app/controllers/application_controller.rb**
201 |
202 | ```ruby
203 | class ApplicationController < ActionController::Base
204 | include TheComments::ViewToken
205 |
206 | # Prevent CSRF attacks by raising an exception.
207 | # For APIs, you may want to use :null_session instead.
208 | protect_from_forgery with: :exception
209 | end
210 | ```
211 |
212 | ### 7. Install assets
213 |
214 | **app/assets/stylesheets/application.css**
215 |
216 | ```css
217 | /*
218 | *= require the_comments
219 | */
220 | ```
221 |
222 | **app/assets/javascripts/application.js**
223 |
224 | ```js
225 | //= require the_comments
226 | ```
227 |
228 | ### 8. Example controller code
229 |
230 | **app/controllers/posts_controller.rb**
231 |
232 | ```ruby
233 | def show
234 | @post = Post.find params[:id]
235 | @comments = @post.comments.with_state([:draft, :published])
236 | end
237 | ```
238 |
239 | ### 9. Example view code
240 |
241 | **app/views/posts/show.html.haml**
242 |
243 | ```haml
244 | = render partial: 'the_comments/tree', locals: { commentable: @post, comments_tree: @comments }
245 | ```
246 |
247 |
248 |
249 | ### Common problems
250 |
251 | For error with `unpermitted parameters` in webserver output.
252 |
253 | Example:
254 |
255 | Unpermitted parameters: commentable_type, commentable_id
256 |
257 | User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
258 |
259 | Completed 500 Internal Server Error in 8ms
260 |
261 | Add the following to your Comments Controller.
262 |
263 | def comment_params
264 | params
265 | .require(:comment)
266 | .permit(:title, :contacts, :raw_content, :parent_id, :commentable_type, :commentable_id)
267 | .merge(denormalized_fields)
268 | .merge(request_data_for_comment)
269 | .merge(tolerance_time: params[:tolerance_time].to_i)
270 | .merge(user: current_user, view_token: comments_view_token)
271 | end
272 |
273 | See [here](https://github.com/the-teacher/the_comments/issues/34).
274 |
275 |
276 |
277 | For errors with `around_validation`.
278 |
279 | Example:
280 |
281 | NoMethodError - protected method `around_validation' called for #:
282 |
283 | Create a new file `config/state_machine.rb`.
284 |
285 | # Rails 4.1.0.rc1 and StateMachine don't play nice
286 | # https://github.com/pluginaweek/state_machine/issues/295
287 |
288 | require 'state_machine/version'
289 |
290 | unless StateMachine::VERSION == '1.2.0'
291 | # If you see this message, please test removing this file
292 | # If it's still required, please bump up the version above
293 | Rails.logger.warn "Please remove me, StateMachine version has changed"
294 | end
295 |
296 | module StateMachine::Integrations::ActiveModel
297 | public :around_validation
298 | end
299 |
300 | See [here](https://github.com/pluginaweek/state_machine/issues/295).
301 |
302 |
303 |
304 | ### Feedback
305 |
306 | :speech_balloon: My twitter: [@iam_teacher](https://twitter.com/iam_teacher) hashtag: **#the_comments**
307 |
308 | ### Acknowledgments
309 |
310 | * Anna Nechaeva (my wife) - for love and my happy life
311 | * @tanraya (Andrew Kozlov) - for code review
312 | * @solenko (Anton Petrunich) - for mountable routes
313 | * @pyromaniac (Arkadiy Zabazhanov) - for consulting
314 |
315 |
316 |
317 | ### MIT License
318 |
319 | Copyright (c) 2013 Ilya N. Zykin
320 |
321 | Permission is hereby granted, free of charge, to any person obtaining
322 | a copy of this software and associated documentation files (the
323 | "Software"), to deal in the Software without restriction, including
324 | without limitation the rights to use, copy, modify, merge, publish,
325 | distribute, sublicense, and/or sell copies of the Software, and to
326 | permit persons to whom the Software is furnished to do so, subject to
327 | the following conditions:
328 |
329 | The above copyright notice and this permission notice shall be
330 | included in all copies or substantial portions of the Software.
331 |
332 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
333 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
334 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
335 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
336 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
337 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
338 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
339 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
--------------------------------------------------------------------------------
/app/assets/javascripts/the_comments.js.coffee:
--------------------------------------------------------------------------------
1 | # ERROR MSG BUILDER
2 | @comments_errors_builder = (errors) ->
3 | error_msgs = ''
4 | for field, errs of errors
5 | for err in errs
6 | error_msgs += "#{ field }: #{ err }
"
7 | error_msgs
8 |
9 | # FORM CLEANER
10 | @clear_comment_form = ->
11 | $('.error_notifier', '#new_comment, .comments_tree').hide()
12 | $("input[name='comment[title]']").val('')
13 | $("textarea[name='comment[raw_content]']").val('')
14 |
15 | # NOTIFIER
16 | @comments_error_notifier = (form, text) ->
17 | form.children('.error_notifier').empty().hide().append(text).show()
18 |
19 | # TIME HELPER
20 | @unixsec = (t) -> Math.round(t.getTime() / 1000)
21 |
22 | # HIGHTLIGHT ANCHOR
23 | @highlight_anchor = ->
24 | hash = document.location.hash
25 | if hash.match('#comment_')
26 | $(hash).addClass 'highlighted'
27 |
28 | $ ->
29 | window.tolerance_time_start = unixsec(new Date)
30 | tolerance_time = $('[data-comments-tolarance-time]').first().data('comments-tolarance-time')
31 |
32 | # Button Click => AJAX Before Send
33 | submits = '#new_comment input[type=submit], .reply_comments_form input[type=submit]'
34 |
35 | $(document).on 'click', submits, (e) ->
36 | button = $ e.target
37 | form = button.parents('form').first()
38 | time_diff = unixsec(new Date) - window.tolerance_time_start
39 |
40 | if tolerance_time && (time_diff < tolerance_time)
41 | delta = tolerance_time - time_diff
42 | error_msgs = comments_errors_builder({ delay: ["Please wait #{delta} secs"] })
43 | comments_error_notifier(form, error_msgs)
44 | return false
45 |
46 | $('.tolerance_time').val time_diff
47 | button.hide()
48 | true
49 |
50 | ################ COMMENTS FORMS ################
51 | comment_forms = "#new_comment, .reply_comments_form"
52 |
53 | # ERROR
54 | $(document).on 'ajax:error', comment_forms, (request, response, status) ->
55 | form = $ @
56 | $('input[type=submit]', form).show()
57 | error_msgs = comments_errors_builder({ "Server Error: ": [response.status] })
58 | comments_error_notifier(form, error_msgs)
59 |
60 | # SUCCESS
61 | $(document).on 'ajax:success', comment_forms, (request, response, status) ->
62 | form = $ @
63 | $('input[type=submit]', form).show()
64 |
65 | if typeof(response) is 'string'
66 | anchor = $(response).find('.comment').attr('id')
67 | clear_comment_form()
68 | form.hide()
69 | $('.parent_id').val('')
70 | $('#new_comment').fadeIn()
71 | tree = form.parent().siblings('.nested_set')
72 | tree = $('ol.comments_tree') if tree.length is 0
73 | tree.append(response)
74 | document.location.hash = anchor
75 | else
76 | error_msgs = comments_errors_builder(response.errors)
77 | comments_error_notifier(form, error_msgs)
78 |
79 | # NEW ROOT BUTTON
80 | $(document).on 'click', '#new_root_comment', ->
81 | $('.reply_comments_form').hide()
82 | $('.parent_id').val('')
83 | $('#new_comment').fadeIn()
84 | false
85 |
86 | # REPLY BUTTON
87 | $(document).on 'click', '.reply_link', ->
88 | link = $ @
89 | comment = link.parent().parent().parent()
90 |
91 | $(comment_forms).hide()
92 | form = $('#new_comment').clone().removeAttr('id').addClass('reply_comments_form')
93 |
94 | comment_id = comment.data('comment-id')
95 | $('.parent_id', form).val comment_id
96 |
97 | comment.siblings('.form_holder').html(form)
98 | $('.error_notifier', form).empty().hide()
99 | form.fadeIn()
100 | false
101 |
102 | $ ->
103 | # ANCHOR HIGHLIGHT
104 | highlight_anchor()
105 |
106 | $(window).on 'hashchange', ->
107 | $('.comment.highlighted').removeClass 'highlighted'
108 | highlight_anchor()
109 |
--------------------------------------------------------------------------------
/app/assets/javascripts/the_comments_manage.js.coffee:
--------------------------------------------------------------------------------
1 | $ ->
2 | hide_comment_panel = (btn) -> $(btn).parents('.panel').slideUp()
3 |
4 | comments = $ '.comments'
5 |
6 | # CONTROLS
7 | comments.on 'click', 'a.additional_info', ->
8 | btn = $ @
9 | holder = btn.parents('.panel-body')
10 | holder.find('div.additional_info').slideToggle()
11 | false
12 |
13 | comments.on 'click', 'a.edit', ->
14 | btn = $ @
15 | holder = btn.parents('.panel-body')
16 | holder.find('.edit_form, .comment_body, a.edit').toggle()
17 | false
18 |
19 | comments.on 'ajax:success', '.to_published, .to_draft, .to_spam, .to_deleted', ->
20 | hide_comment_panel @
21 |
22 | # Edit form
23 | comments.on 'ajax:success', '.edit_comment', (request, response, status) ->
24 | form = $ @
25 | holder = form.parents('.panel-body')
26 | holder.find('.edit_form, .comment_body, a.edit').toggle()
27 | holder.find('.comment_body').replaceWith response
--------------------------------------------------------------------------------
/app/assets/stylesheets/the_comments.css.scss:
--------------------------------------------------------------------------------
1 | .comments_tree, .comments_list{
2 | font-family: Arial;
3 |
4 | margin:0;
5 | padding:0;
6 | margin-bottom: 30px;
7 |
8 | *{ margin: 0; padding: 0; font-size: inherit; }
9 |
10 | a{ text-decoration: none; }
11 | a:hover{ text-decoration: underline; }
12 |
13 | ol{
14 | margin: 0;
15 | padding: 0 0 0 20px;
16 | list-style: none outside none;
17 | }
18 | li{
19 | margin-bottom: 5px;
20 | position: relative;
21 | list-style: none outside none;
22 | }
23 | }
24 |
25 | .action_btns a{ margin-right: 15px; }
26 |
27 | .comments, .comments_tree{
28 | font-family: Arial;
29 | font-size: 13px;
30 |
31 | h3{ font-size: 1.6em; }
32 |
33 | .error_notifier{
34 | background-color: #F2DEDE;
35 | border: 1px solid #B94A48;
36 | color: #B94A48;
37 |
38 | border-radius: 4px;
39 | margin: 0 0 15px 0;
40 | padding: 10px 10px 0 10px;
41 | overflow: hidden;
42 |
43 | p{ margin: 0 0 10px 0; }
44 | }
45 | form{
46 |
47 | background: #e0e4f5;
48 |
49 | border: 1px solid #c6cff5;
50 | border-radius: 5px;
51 | padding: 10px;
52 |
53 | p{ margin: 0 0 10px 0; }
54 |
55 | input[type=text]{
56 | border: 1px solid gray;
57 | padding: 4px;
58 | width: 75%;
59 | }
60 | label{ font-size: 15px; }
61 | textarea{
62 | border: 1px solid gray;
63 | font-family: Arial;
64 | font-size: 13px;
65 | height: 150px;
66 | padding: 4px;
67 | width: 75%;
68 | }
69 | .trap{
70 | margin: 0; padding: 0;
71 | filter: alpha(opacity=0.001);
72 | height: 0.1px;
73 | opacity: 0.001;
74 | overflow: hidden;
75 | }
76 | }
77 | }
78 |
79 | .comments_tree{
80 | .nested_set{
81 | border-left: 1px dotted lightGray;
82 | }
83 |
84 | li{
85 | .comment.draft{
86 | border: 1px solid gray;
87 | background: #eff5f3;
88 | padding: 10px;
89 | }
90 | }
91 |
92 | .form_holder{ margin-left: 40px; }
93 |
94 | .edit, .delete{
95 | margin-bottom: 3px;
96 | text-align:center;
97 | line-height: 130%;
98 | background: #336;
99 | color: white;
100 | padding: 1px;
101 | }
102 | .delete{ background: gray; }
103 |
104 | .comment{
105 | overflow: hidden; zoom: 1;
106 |
107 | .userpic{
108 | overflow: hidden; zoom: 1;
109 | float: left;
110 | width: 50px;
111 |
112 | img{ margin-bottom: 10px; width: 42px; height: 42px; }
113 | }
114 | .userbar, .cbody{
115 | margin: 0 0 5px 55px;
116 | padding: 3px;
117 | }
118 | .userbar{
119 | background: #eff5f3;
120 | border-radius: 3px;
121 | padding-left: 7px;
122 | }
123 | &.draft{
124 | .userbar{ background: #ffa768; }
125 | .to_draft{ display: none; }
126 | }
127 | &.published{
128 | .to_published{ display: none; }
129 | }
130 | .cbody{
131 | font-size: 15px;
132 | border-bottom: 1px solid #eee;
133 | padding-bottom: 10px;
134 | line-height: 135%;
135 | margin-bottom: 3px;
136 | overflow: hidden;
137 | }
138 | .reply{
139 | margin: 0 0 5px 55px;
140 | font-size: 12px;
141 | }
142 | }
143 |
144 | .controls{
145 | position: absolute;
146 | top: 53px; left: 5px;
147 |
148 | a{
149 | font-size:11px;
150 | display:block;
151 | }
152 | }
153 |
154 | .comment{
155 | margin-bottom: 10px;
156 |
157 | &.published, &.draft{
158 | margin-bottom: 10px;
159 | border-radius: 3px;
160 | padding: 5px;
161 | }
162 | &.highlighted{ border: 1px dashed #ff6633 !important; }
163 |
164 | }
165 |
166 | .form_holder{
167 | form{ margin: 10px 0; }
168 | }
169 | }
170 |
171 | .new_comment{
172 | .btn{
173 | padding: 7px;
174 | margin-top: 5px;
175 | cursor: pointer;
176 | }
177 | }
178 |
179 | .comments_list{
180 | li{
181 | margin-bottom: 20px;
182 |
183 | .item{
184 | border: 1px solid gray;
185 | border-radius: 5px;
186 | padding: 10px;
187 | }
188 |
189 | .draft{
190 | border-left: 5px solid orange;
191 |
192 | .controls{
193 | a.to_draft{ display: none }
194 | }
195 | }
196 | .published{
197 | border-left: 5px solid green;
198 |
199 | .controls{
200 | a.to_published{ display: none; }
201 | }
202 | }
203 |
204 | .deleted{
205 | border-left: 5px solid red;
206 |
207 | .controls{
208 | a.to_deleted, a.to_spam{ display: none; }
209 | }
210 | }
211 |
212 | .comment{
213 | div{ margin: 0 0 10px 0; }
214 |
215 | label{
216 | width: 75px;
217 | font-weight: bold;
218 | display: inline-block;
219 | }
220 | input[type=text]{
221 | width: 70%;
222 | padding: 4px;
223 | }
224 | input[type=submit]{
225 | padding: 5px;
226 | cursor: pointer;
227 | }
228 | textarea{
229 | width: 70%;
230 | padding: 4px;
231 | height: 150px;
232 | }
233 | .content{
234 | line-height: 130%;
235 | background: #ddd;
236 | padding: 10px;
237 | }
238 | .commentable{
239 | margin-bottom: 10px;
240 | }
241 | .controls{
242 | background: lightgray;
243 | padding: 3px;
244 | a{ margin-right: 15px; }
245 | }
246 | }
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/app/controllers/_templates_/comments_controller.rb:
--------------------------------------------------------------------------------
1 | class CommentsController < ApplicationController
2 | # layout 'admin'
3 |
4 | # Define your restrict methods and use them like this:
5 | #
6 | # before_action :user_required, except: %w[index create]
7 | # before_action :owner_required, except: %w[index create]
8 | # before_action :admin_required, only: %w[total_draft total_published total_deleted total_spam]
9 |
10 | include TheComments::Controller
11 |
12 | # >>> include TheComments::Controller <<<
13 | # (!) Almost all methods based on *current_user* method
14 | #
15 | # 1. Controller's public methods list:
16 | # You can redifine it for your purposes
17 | # public
18 | # %w[ manage index create edit update ]
19 | # %w[ my_comments my_draft my_published ]
20 | # %w[ draft published deleted spam ]
21 | # %w[ to_draft to_published to_deleted to_spam ]
22 | # %w[ total_draft total_published total_deleted total_spam ]
23 | #
24 | #
25 | # 2. Controller's private methods list:
26 | # You can redifine it for your purposes
27 | #
28 | # private
29 | # %w[ comment_template comment_partial ]
30 | # %w[ denormalized_fields request_data_for_comment define_commentable ]
31 | # %w[ comment_params patch_comment_params ]
32 | # %w[ ajax_requests_required cookies_required ]
33 | # %w[ empty_trap_required tolerance_time_required ]
34 |
35 | # KAMINARI pagination:
36 | # following methods based on gem "kaminari"
37 | # You should redefine them if you use something else
38 | #
39 | # public
40 | # %w[ manage index edit ]
41 | # %w[ draft published deleted spam ]
42 | # %w[ my_comments my_draft my_published ]
43 | # %w[ total_draft total_published total_deleted total_spam ]
44 | end
--------------------------------------------------------------------------------
/app/controllers/concerns/the_comments/controller.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | # Base functionality of Comments Controller
3 | # class CommentsController < ApplicationController
4 | # include TheComments::Controller
5 | # end
6 | module Controller
7 | extend ActiveSupport::Concern
8 |
9 | included do
10 | include TheComments::ViewToken
11 |
12 | # Attention! We should not set TheComments cookie before create
13 | skip_before_action :set_the_comments_cookies, only: [:create]
14 |
15 | # Spam protection
16 | before_action -> { @errors = [] }, only: [:create]
17 |
18 | before_action :ajax_requests_required, only: [:create]
19 | before_action :cookies_required, only: [:create]
20 |
21 | before_action :empty_trap_required, only: [:create], if: -> { TheComments.config.empty_trap_protection }
22 | before_action :tolerance_time_required, only: [:create], if: -> { TheComments.config.tolerance_time_protection }
23 |
24 | # preparation
25 | before_action :define_commentable, only: [:create]
26 |
27 | # raise an errors
28 | before_action -> { return render(json: { errors: @errors }) unless @errors.blank? }, only: [:create]
29 | end
30 |
31 | # App side methods (you can overwrite them)
32 |
33 | def manage
34 | @comments = current_user.comcoms.with_users.active.recent.page(params[:page])
35 | render comment_template(:manage)
36 | end
37 |
38 | def my_comments
39 | @comments = current_user.my_comments.with_users.active.recent.page(params[:page])
40 | render comment_template(:manage)
41 | end
42 |
43 | # Methods based on *current_user* helper
44 | # Methods for admin
45 | %w[draft published deleted].each do |state|
46 | define_method "#{state}" do
47 | @comments = current_user.comcoms.with_users.with_state(state).recent.page(params[:page])
48 | render comment_template(:manage)
49 | end
50 |
51 | define_method "total_#{state}" do
52 | @comments = ::Comment.with_state(state).with_users.recent.page(params[:page])
53 | render comment_template(:manage)
54 | end
55 |
56 | define_method "my_#{state}" do
57 | @comments = current_user.my_comments.with_users.with_state(state).recent.page(params[:page])
58 | render comment_template(:manage)
59 | end
60 | end
61 |
62 | def spam
63 | @comments = current_user.comcoms.with_users.where(spam: true).recent.page(params[:page])
64 | render comment_template(:manage)
65 | end
66 |
67 | def my_spam
68 | @comments = current_user.my_comments.with_users.where(spam: true).recent.page(params[:page])
69 | render comment_template(:manage)
70 | end
71 |
72 | def total_spam
73 | @comments = ::Comment.where(spam: true).with_users.recent.page(params[:page])
74 | render comment_template(:manage)
75 | end
76 |
77 | # BASE METHODS
78 |
79 | # Public methods
80 |
81 | def create
82 | @comment = @commentable.comments.new comment_params
83 | if @comment.valid?
84 | @comment.save
85 | return render layout: false, partial: comment_partial(:comment), locals: { tree: @comment }
86 | end
87 | render json: { errors: @comment.errors }
88 | end
89 |
90 | # Restricted area
91 |
92 | def edit
93 | @comments = current_user.comcoms.where(id: params[:id]).page(params[:page])
94 | render comment_template(:manage)
95 | end
96 |
97 | def update
98 | comment = ::Comment.find(params[:id])
99 | comment.update_attributes!(patch_comment_params)
100 | render(layout: false, partial: comment_partial(:comment_body), locals: { comment: comment })
101 | end
102 |
103 | %w[draft published deleted].each do |state|
104 | define_method "to_#{state}" do
105 | ::Comment.find(params[:id]).try "to_#{state}"
106 | render nothing: true
107 | end
108 | end
109 |
110 | def to_spam
111 | comment = ::Comment.find(params[:id])
112 | comment.to_spam
113 | comment.to_deleted
114 | render nothing: true
115 | end
116 |
117 | private
118 |
119 | def comment_template template
120 | { template: "the_comments/#{TheComments.config.template_engine}/#{template}" }
121 | end
122 |
123 | def comment_partial partial
124 | "the_comments/#{TheComments.config.template_engine}/#{partial}"
125 | end
126 |
127 | def denormalized_fields
128 | title = @commentable.commentable_title
129 | url = @commentable.commentable_url
130 | @commentable ? { commentable_title: title, commentable_url: url } : {}
131 | end
132 |
133 | def request_data_for_comment
134 | r = request
135 | { ip: r.ip, referer: CGI::unescape(r.referer || 'direct_visit'), user_agent: r.user_agent }
136 | end
137 |
138 | def define_commentable
139 | commentable_klass = params[:comment][:commentable_type].constantize
140 | commentable_id = params[:comment][:commentable_id]
141 |
142 | @commentable = commentable_klass.find(commentable_id)
143 | return render(json: { errors: [t('the_comments.undefined_commentable')] }) unless @commentable
144 | end
145 |
146 | def comment_params
147 | params
148 | .require(:comment)
149 | .permit(:title, :contacts, :raw_content, :parent_id)
150 | .merge(denormalized_fields)
151 | .merge(request_data_for_comment)
152 | .merge(tolerance_time: params[:tolerance_time].to_i)
153 | .merge(user: current_user, view_token: comments_view_token)
154 | end
155 |
156 | def patch_comment_params
157 | params
158 | .require(:comment)
159 | .permit(:title, :contacts, :raw_content, :parent_id)
160 | end
161 |
162 | # Protection hooks
163 | def ajax_requests_required
164 | unless request.xhr?
165 | return render(text: t('the_comments.ajax_requests_required'))
166 | end
167 | end
168 |
169 | def cookies_required
170 | if cookies[:the_comment_cookies] != TheComments::COMMENTS_COOKIES_TOKEN
171 | @errors << [t('the_comments.cookies'), t('the_comments.cookies_required')].join(': ')
172 | end
173 | end
174 |
175 | # TODO:
176 | # 1) inject ?
177 | # 2) fields can be removed on client side
178 | def empty_trap_required
179 | is_human = true
180 | params.slice(*TheComments.config.empty_inputs).values.each{|v| is_human = (is_human && v.blank?) }
181 |
182 | if !is_human
183 | @errors << [t('the_comments.trap'), t('the_comments.trap_message')].join(': ')
184 | end
185 | end
186 |
187 | def tolerance_time_required
188 | this_time = params[:tolerance_time].to_i
189 | min_time = TheComments.config.tolerance_time.to_i
190 |
191 | if this_time < min_time
192 | tdiff = min_time - this_time
193 | @errors << [t('the_comments.tolerance_time'), t('the_comments.tolerance_time_message', time: tdiff )].join(': ')
194 | end
195 | end
196 | end
197 | end
198 |
--------------------------------------------------------------------------------
/app/controllers/concerns/the_comments/view_token.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | # Cookies and View token for spam protection
3 | # include TheComments::ViewToken
4 | module ViewToken
5 | extend ActiveSupport::Concern
6 |
7 | included { before_action :set_the_comments_cookies }
8 |
9 | def comments_view_token
10 | cookies[:comments_view_token]
11 | end
12 |
13 | private
14 |
15 | def set_the_comments_cookies
16 | cookies[:the_comment_cookies] = { value: TheComments::COMMENTS_COOKIES_TOKEN, expires: 1.year.from_now }
17 | cookies[:comments_view_token] = { value: SecureRandom.hex, expires: 7.days.from_now } unless cookies[:comments_view_token]
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/helpers/render_comments_tree_helper.rb:
--------------------------------------------------------------------------------
1 | # coding: UTF-8
2 | # DOC:
3 | # We use Helper Methods for tree building,
4 | # because it's faster than View Templates and Partials
5 |
6 | # SECURITY note
7 | # Prepare your data on server side for rendering
8 | # or use h.html_escape(node.content)
9 | # for escape potentially dangerous content
10 | module RenderCommentsTreeHelper
11 | module Render
12 | class << self
13 | attr_accessor :h, :options
14 |
15 | # Main Helpers
16 | def controller
17 | @options[:controller]
18 | end
19 |
20 | def t str
21 | controller.t str
22 | end
23 |
24 | # Render Helpers
25 | def visible_draft?
26 | controller.try(:comments_view_token) == @comment.view_token
27 | end
28 |
29 | def moderator?
30 | controller.try(:current_user).try(:comments_moderator?, @comment)
31 | end
32 |
33 | # Render Methods
34 | def render_node(h, options)
35 | @h, @options = h, options
36 | @comment = options[:node]
37 |
38 | @max_reply_depth = options[:max_reply_depth] || TheComments.config.max_reply_depth
39 |
40 | if @comment.draft?
41 | draft_comment
42 | else @comment.published?
43 | published_comment
44 | end
45 | end
46 |
47 | def draft_comment
48 | if visible_draft? || moderator?
49 | published_comment
50 | else
51 | "
52 |
56 | #{ children }
57 | "
58 | end
59 | end
60 |
61 | def published_comment
62 | "
63 |
71 |
72 |
73 | #{ children }
74 | "
75 | end
76 |
77 | def avatar
78 | "
79 |

80 | #{ controls }
81 |
"
82 | end
83 |
84 | def userbar
85 | anchor = h.link_to('#', '#comment_' + @comment.anchor)
86 | title = @comment.title.blank? ? t('the_comments.guest_name') : @comment.title
87 | "#{ title } #{ anchor }
"
88 | end
89 |
90 | def moderator_controls
91 | if moderator?
92 | h.link_to(t('the_comments.edit'), h.edit_comment_url(@comment), class: :edit)
93 | end
94 | end
95 |
96 | def reply
97 | if @comment.depth < (@max_reply_depth - 1)
98 | "#{ t('the_comments.reply') }"
99 | end
100 | end
101 |
102 | def controls
103 | "
#{ moderator_controls }
"
104 | end
105 |
106 | def children
107 | "#{ options[:children] }
"
108 | end
109 | end
110 | end
111 | end
--------------------------------------------------------------------------------
/app/models/_templates_/comment.rb:
--------------------------------------------------------------------------------
1 | class Comment < ActiveRecord::Base
2 | include TheComments::Comment
3 | # ---------------------------------------------------
4 | # Define comment's avatar url
5 | # Usually we use Comment#user (owner of comment) to define avatar
6 | # @blog.comments.includes(:user) <= use includes(:user) to decrease queries count
7 | # comment#user.avatar_url
8 | # ---------------------------------------------------
9 |
10 | # public
11 | # ---------------------------------------------------
12 | # Simple way to define avatar url
13 | #
14 | # def avatar_url
15 | # src = id.to_s
16 | # src = title unless title.blank?
17 | # src = contacts if !contacts.blank? && /@/ =~ contacts
18 | # hash = Digest::MD5.hexdigest(src)
19 | # "https://2.gravatar.com/avatar/#{hash}?s=42&d=https://identicons.github.com/#{hash}.png"
20 | # end
21 | # ---------------------------------------------------
22 |
23 | # private
24 | # ---------------------------------------------------
25 | # Define your content filters
26 | # gem 'RedCloth'
27 | # gem 'sanitize'
28 | # gem 'MySmilesProcessor'
29 | #
30 | # def prepare_content
31 | # text = self.raw_content
32 | # text = RedCloth.new(text).to_html
33 | # text = MySmilesProcessor.new(text)
34 | # text = Sanitize.clean(text, Sanitize::Config::RELAXED)
35 | # self.content = text
36 | # end
37 | # ---------------------------------------------------
38 | end
--------------------------------------------------------------------------------
/app/models/concerns/the_comments/comment.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | module Comment
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | scope :active, -> { with_state [:draft, :published] }
7 | scope :with_users, -> { includes(:user) }
8 |
9 | # Nested Set
10 | acts_as_nested_set scope: [:commentable_type, :commentable_id]
11 |
12 | # simple sort scopes
13 | include ::TheSimpleSort::Base
14 |
15 | # TheSortableTree
16 | include ::TheSortableTree::Scopes
17 |
18 | # Comments State Machine
19 | include TheComments::CommentStates
20 |
21 | validates :raw_content, presence: true
22 |
23 | # relations
24 | belongs_to :user
25 | belongs_to :holder, class_name: :User
26 | belongs_to :commentable, polymorphic: true
27 |
28 | # callbacks
29 | before_create :define_holder, :define_default_state, :define_anchor, :denormalize_commentable
30 | after_create :update_cache_counters
31 | before_save :prepare_content
32 | end
33 |
34 | def header_title
35 | title.present? ? title : I18n.t('the_comments.guest_name')
36 | end
37 |
38 | def user_name
39 | user.try(:username) || user.try(:login) || header_title
40 | end
41 |
42 | def avatar_url
43 | src = id.to_s
44 | src = title unless title.blank?
45 | src = contacts if !contacts.blank? && /@/ =~ contacts
46 | hash = Digest::MD5.hexdigest(src)
47 | "https://2.gravatar.com/avatar/#{hash}?s=42&d=https://identicons.github.com/#{hash}.png"
48 | end
49 |
50 | def mark_as_spam
51 | count = self_and_descendants.update_all({spam: true})
52 | update_spam_counter
53 | count
54 | end
55 |
56 | def mark_as_not_spam
57 | count = self_and_descendants.update_all({spam: false})
58 | update_spam_counter
59 | count
60 | end
61 |
62 | def to_spam
63 | mark_as_spam
64 | end
65 |
66 | private
67 |
68 | def update_spam_counter
69 | holder.try :update_comcoms_spam_counter
70 | end
71 |
72 | def define_anchor
73 | self.anchor = SecureRandom.hex[0..5]
74 | end
75 |
76 | def define_holder
77 | c = self.commentable
78 | self.holder = c.is_a?(User) ? c : c.try(:user)
79 | end
80 |
81 | def define_default_state
82 | self.state = TheComments.config.default_owner_state if user && user == holder
83 | end
84 |
85 | def denormalize_commentable
86 | self.commentable_title = commentable.try :commentable_title
87 | self.commentable_state = commentable.try :commentable_state
88 | self.commentable_url = commentable.try :commentable_url
89 | end
90 |
91 | def prepare_content
92 | self.content = self.raw_content
93 | end
94 |
95 | # Warn: increment! doesn't call validation =>
96 | # before_validation filters doesn't work =>
97 | # We have few unuseful requests
98 | # I impressed that I found it and reduce DB requests
99 | # Awesome logic pazzl! I'm really pedant :D
100 | def update_cache_counters
101 | user.try :recalculate_my_comments_counter!
102 |
103 | if holder
104 | holder.send :try, :define_denormalize_flags
105 | holder.increment! "#{ state }_comcoms_count"
106 | # holder.class.increment_counter("#{ state }_comcoms_count", holder.id)
107 | end
108 |
109 | if commentable
110 | commentable.send :define_denormalize_flags
111 | commentable.increment! "#{ state }_comments_count"
112 | # holder.class.increment_counter("#{ state }_comments_count", holder.id)
113 | end
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/app/models/concerns/the_comments/comment_states.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | module CommentStates
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | # :draft | :published | :deleted
7 | state_machine :state, :initial => TheComments.config.default_state do
8 |
9 | # events
10 | event :to_draft do
11 | transition all - :draft => :draft
12 | end
13 |
14 | event :to_published do
15 | transition all - :published => :published
16 | end
17 |
18 | event :to_deleted do
19 | transition any - :deleted => :deleted
20 | end
21 |
22 | # transition callbacks
23 | after_transition any => any do |comment|
24 | @comment = comment
25 | @owner = comment.user
26 | @holder = comment.holder
27 | @commentable = comment.commentable
28 | end
29 |
30 | # between draft and published
31 | after_transition [:draft, :published] => [:draft, :published] do |comment, transition|
32 | from = transition.from_name
33 | to = transition.to_name
34 |
35 | if @holder
36 | @holder.send :try, :define_denormalize_flags
37 | @holder.increment! "#{to}_comcoms_count"
38 | @holder.decrement! "#{from}_comcoms_count"
39 | end
40 |
41 | if @commentable
42 | @commentable.send :define_denormalize_flags
43 | @commentable.increment! "#{to}_comments_count"
44 | @commentable.decrement! "#{from}_comments_count"
45 | end
46 | end
47 |
48 | # to deleted (cascade like query)
49 | after_transition [:draft, :published] => :deleted do |comment|
50 | ids = comment.self_and_descendants.map(&:id)
51 | ::Comment.where(id: ids).update_all(state: :deleted)
52 |
53 | @owner.try :recalculate_my_comments_counter!
54 | @holder.try :recalculate_comcoms_counters!
55 | @commentable.try :recalculate_comments_counters!
56 | end
57 |
58 | # from deleted
59 | after_transition :deleted => [:draft, :published] do |comment, transition|
60 | to = transition.to_name
61 | comment.mark_as_not_spam
62 |
63 | @owner.try :recalculate_my_comments_counter!
64 |
65 | if @holder
66 | @holder.send :try, :define_denormalize_flags
67 | @holder.decrement! :deleted_comcoms_count
68 | @holder.increment! "#{to}_comcoms_count"
69 | end
70 |
71 | if @commentable
72 | @commentable.send :define_denormalize_flags
73 | @commentable.decrement! :deleted_comments_count
74 | @commentable.increment! "#{to}_comments_count"
75 | end
76 | end
77 | end
78 | end
79 | end
80 | end
--------------------------------------------------------------------------------
/app/models/concerns/the_comments/commentable.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | module Commentable
3 |
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | has_many :comments, as: :commentable
8 |
9 | # *define_denormalize_flags* - should be placed before title or url builder filters
10 | before_validation :define_denormalize_flags
11 | after_save :denormalize_for_comments, if: -> { !id_changed? }
12 | end
13 |
14 | # Default Denormalization methods
15 | # Overwrite it with your Application
16 | def commentable_title
17 | # My first blog post
18 | try(:title) || TheComments.config.default_title
19 | end
20 |
21 | def commentable_url
22 | # /posts/1
23 | ['', self.class.to_s.tableize, self.to_param].join('/')
24 | end
25 |
26 | def commentable_state
27 | # 'draft'
28 | try(:state)
29 | end
30 |
31 | # Helper methods
32 | def comments_sum
33 | published_comments_count + draft_comments_count
34 | end
35 |
36 | def recalculate_comments_counters!
37 | update_attributes!({
38 | draft_comments_count: comments.with_state(:draft).count,
39 | published_comments_count: comments.with_state(:published).count,
40 | deleted_comments_count: comments.with_state(:deleted).count
41 | })
42 | end
43 |
44 | private
45 |
46 | def define_denormalize_flags
47 | @trackable_commentable_title = commentable_title
48 | @trackable_commentable_state = commentable_state
49 | @trackable_commentable_url = commentable_url
50 | end
51 |
52 | def denormalization_fields_changed?
53 | a = @trackable_commentable_title != commentable_title
54 | b = @trackable_commentable_state != commentable_state
55 | c = @trackable_commentable_url != commentable_url
56 | a || b || c
57 | end
58 |
59 | def denormalize_for_comments
60 | if denormalization_fields_changed?
61 | comments.update_all({
62 | commentable_title: commentable_title,
63 | commentable_state: commentable_state,
64 | commentable_url: commentable_url
65 | })
66 | end
67 | end
68 | end
69 | end
--------------------------------------------------------------------------------
/app/models/concerns/the_comments/user.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | module User
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | has_many :comcoms, class_name: :Comment, foreign_key: :holder_id
7 | end
8 |
9 | def my_comments; ::Comment.where(user: self); end
10 |
11 | %w[draft published deleted].each do |state|
12 | define_method "my_#{state}_comments" do
13 | my_comments.with_state state
14 | end
15 |
16 | define_method "#{state}_comcoms" do
17 | comcoms.with_state state
18 | end
19 | end
20 |
21 | def my_spam_comments
22 | my_comments.where(spam: true)
23 | end
24 |
25 | # I think we shouldn't to have my_deleted_comments cache counter
26 | def recalculate_my_comments_counter!
27 | dcount = my_draft_comments.count
28 | pcount = my_published_comments.count
29 | update_attributes!({
30 | my_draft_comments_count: dcount,
31 | my_published_comments_count: pcount,
32 | my_comments_count: dcount + pcount
33 | })
34 | end
35 |
36 | def recalculate_comcoms_counters!
37 | update_attributes!({
38 | draft_comcoms_count: draft_comcoms.count,
39 | published_comcoms_count: published_comcoms.count,
40 | deleted_comcoms_count: deleted_comcoms.count
41 | })
42 | end
43 |
44 | def update_comcoms_spam_counter
45 | update!(spam_comcoms_count: comcoms.where(spam: true).count)
46 | end
47 |
48 | def comments_sum
49 | published_comments_count + draft_comments_count
50 | end
51 |
52 | def comcoms_sum
53 | published_comcoms_count + draft_comcoms_count
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/app/views/the_comments/_tree.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "the_comments/#{TheComments.config.template_engine}/tree",
2 | locals: { commentable: commentable, comments_tree: comments_tree }
3 | %>
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_additional_info.html.haml:
--------------------------------------------------------------------------------
1 | .additional_info{ style: "display:none" }
2 | %br
3 | %table.table.table-striped.table-hover
4 | %tr
5 | %th Tolerance time:
6 | %th IP:
7 | %th User Agent:
8 | %th Referer:
9 | %tr
10 | %td= comment.tolerance_time || :none
11 | %td= comment.ip
12 | %td= comment.user_agent
13 | %td= comment.referer
14 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_comment.html.haml:
--------------------------------------------------------------------------------
1 | = build_server_tree(tree, render_module: RenderCommentsTreeHelper, controller: controller)
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_comment_body.html.haml:
--------------------------------------------------------------------------------
1 | %table.comment_body.table.table-striped.table-hover
2 | %tr
3 | %td{ style: "width: 120px;"}
4 | %b= comment.commentable_type
5 | →
6 | %td
7 | = link_to comment.commentable_title, comment.commentable_url
8 | (#{comment.try(:commentable_state)})
9 | %tr
10 | %td
11 | %b= t('the_comments.title')
12 | %td
13 | - if comment.try(:user)
14 | = link_to comment.user_name, comment.user
15 | - else
16 | = comment.header_title
17 | %tr
18 | %td
19 | %b= t('the_comments.contacts')
20 | %td= comment.contacts
21 | %tr.success
22 | %td
23 | %b= t('the_comments.content')
24 | %td{ style: 'word-break: break-all;' }= raw comment.content
25 |
26 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_comment_edit.html.haml:
--------------------------------------------------------------------------------
1 | .edit_form{ style: "display:none" }
2 | = form_for comment, remote: true, html:{ role: :form } do |f|
3 | %table.table.table-striped.table-hover
4 | %tr{ style: "width: 100px;"}
5 | %td
6 | %b= comment.commentable_type
7 | →
8 | %td
9 | = link_to comment.commentable_title, comment.commentable_url
10 | (#{comment.try(:commentable_state)})
11 | %tr
12 | %td
13 | %b= t('the_comments.title')
14 | %td= f.text_field :title, class: "form-control"
15 | %tr
16 | %td
17 | %b= t('the_comments.contacts')
18 | %td= f.text_field :contacts, class: "form-control"
19 | %tr
20 | %td
21 | %b= t('the_comments.content')
22 | %td= f.text_area :raw_content, class: "form-control", rows: 7
23 | %tr
24 | %td
25 | %td= f.submit t('the_comments.update'), class: "btn btn-success"
26 | %hr
27 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_form.html.haml:
--------------------------------------------------------------------------------
1 | %h3
2 | = link_to t('the_comments.new'), '#', id: :new_root_comment
3 |
4 | = form_for Comment.new, remote: true, authenticity_token: true do |f|
5 | - if current_user
6 | = render partial: 'the_comments/haml/logined_form', locals: { f: f, commentable: commentable }
7 | - else
8 | = render partial: 'the_comments/haml/guest_form', locals: { f: f, commentable: commentable }
9 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_guest_form.html.haml:
--------------------------------------------------------------------------------
1 | %label= t('the_comments.form.title')
2 | %p= f.text_field :title, class: 'form-control'
3 |
4 | %label= t('the_comments.form.contacts')
5 | %p= f.text_field :contacts, class: 'form-control'
6 |
7 | %label= t('the_comments.form.content')
8 | %p= f.text_area :raw_content, class: 'form-control'
9 |
10 | %p.trap
11 | - TheComments.config.empty_inputs.each do |name|
12 | = text_field_tag name, nil, autocomplete: :off, tabindex: -1, id: nil
13 |
14 | = hidden_field_tag :tolerance_time, 0, id: nil, class: :tolerance_time
15 |
16 | = f.hidden_field :commentable_type, value: commentable.class
17 | = f.hidden_field :commentable_id, value: commentable.id
18 | = f.hidden_field :parent_id, class: :parent_id
19 |
20 | %p
21 | = f.submit t('the_comments.form.create'), class: :btn
22 | = t('the_comments.form.thank_you')
23 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_logined_form.html.haml:
--------------------------------------------------------------------------------
1 | .logined_comment_form
2 | .user_data
3 | = link_to user_path(current_user), nopin: :nopin do
4 | = image_tag current_user.try(:avatar).try(:url, :thumb)
5 | .comment_data
6 | = hidden_field_tag :tolerance_time, 0, id: nil, class: :tolerance_time
7 | = f.hidden_field :commentable_type, value: commentable.class
8 | = f.hidden_field :commentable_id, value: commentable.id
9 | = f.hidden_field :parent_id, class: :parent_id
10 |
11 | .user_name
12 | %b= current_user.username.present? ? current_user.username : current_user.login
13 | %label= t('the_comments.form.content')
14 | %p= f.text_area :raw_content
15 |
16 | %p
17 | = f.submit t('the_comments.form.create'), class: :btn
18 | = t('the_comments.form.thank_you')
19 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_manage_controls.html.haml:
--------------------------------------------------------------------------------
1 | - hidden = "display:none"
2 |
3 | .row.controls
4 | .col-md-9.action_btns
5 | = link_to '#', class: "edit btn btn-success" do
6 | = t('the_comments.edit')
7 |
8 | = link_to '#', class: "btn btn-warning edit", style: "display:none" do
9 | = t('the_comments.cancel')
10 |
11 | - unless to_hide = comment.published? ? hidden : nil
12 | - opts = { remote: true, style: to_hide, method: :post }
13 | = link_to [:to_published, comment], opts.merge(class: "btn btn-primary to_published") do
14 | = t('the_comments.to_published')
15 |
16 | - unless to_hide = comment.draft? ? hidden : nil
17 | - opts = { remote: true, style: to_hide, method: :post }
18 | = link_to [:to_draft, comment], opts.merge(class: "btn btn-warning to_draft") do
19 | = t('the_comments.to_draft')
20 |
21 | - unless to_hide = comment.deleted? ? hidden : nil
22 | - opts = { remote: true, style: to_hide, method: :delete, data: { confirm: t('the_comments.delete_confirm') } }
23 | = link_to [:to_deleted, comment], opts.merge(class: "btn btn-danger to_deleted") do
24 | = t('the_comments.to_deleted')
25 |
26 | - opts = { remote: true, method: :post}
27 | = link_to [:to_spam, comment], opts.merge(class: "btn btn-danger to_spam") do
28 | = t('the_comments.to_spam')
29 | .col-md-3.text-right
30 | = link_to t('the_comments.additional_info'), "#", class: "additional_info btn btn-info"
31 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_sidebar.html.haml:
--------------------------------------------------------------------------------
1 | .panel.panel-default
2 | .panel-heading= t "the_comments.nav.header"
3 | .panel-body
4 | = render partial: 'the_comments/haml/sidebar_backlink'
5 |
6 | - if current_user.comments_admin?
7 | = render partial: 'the_comments/haml/sidebar_admin'
8 | - else
9 | = render partial: 'the_comments/haml/sidebar_user'
10 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_sidebar_admin.html.haml:
--------------------------------------------------------------------------------
1 | - cuser = current_user
2 |
3 | = render partial: 'the_comments/haml/sidebar_user'
4 |
5 | - if cuser.comments_admin?
6 | %br
7 | %h5= t 'the_comments.in_system', num: Comment.count
8 | %p= link_to t("the_comments.published_comments", num: Comment.with_state(:published).count), [:total_published, :comments], class: 'btn btn-success btn-sm'
9 | %p= link_to t("the_comments.draft_comments", num: Comment.with_state(:draft).count), [:total_draft, :comments], class: 'btn btn-info btn-sm'
10 | %p
11 | = link_to t("the_comments.deleted_comments", num: Comment.with_state(:deleted).count), [:total_deleted, :comments], class: 'btn btn-default btn-sm'
12 | = link_to t("the_comments.spam_comments", num: Comment.where(spam: true).count), [:total_spam, :comments], class: 'btn btn-default btn-sm'
13 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_sidebar_backlink.html.haml:
--------------------------------------------------------------------------------
1 | %p= link_to t('the_comments.nav.to_root'), root_path
2 | %p= link_to t('the_comments.nav.all_incoming'), manage_comments_url
3 | %hr
4 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_sidebar_user.html.haml:
--------------------------------------------------------------------------------
1 | - cuser = current_user
2 |
3 | %h5=t 'the_comments.written_by_me', num: cuser.my_comments.count
4 |
5 | %p= link_to t("the_comments.published_comments", num: cuser.my_published_comments.count), [:my_published, :comments], class: 'btn btn-success btn-sm'
6 | %p= link_to t("the_comments.draft_comments", num: cuser.my_draft_comments.count), [:my_draft, :comments], class: 'btn btn-info btn-sm'
7 |
8 | %p
9 | - if cuser.comments_admin?
10 | = link_to t("the_comments.deleted_comments", num: cuser.my_deleted_comments.count), [:my_deleted, :comments], class: 'btn btn-default btn-sm'
11 | = link_to t("the_comments.spam_comments", num: cuser.my_spam_comments.count), [:my_spam, :comments], class: 'btn btn-default btn-sm'
12 | - else
13 | %span.btn.btn-default.btn-sm= t("the_comments.deleted_comments", num: cuser.my_deleted_comments.count)
14 | %span.btn.btn-default.btn-sm= t("the_comments.spam_comments", num: cuser.my_spam_comments.count)
15 |
16 | %br
17 | %h5= t 'the_comments.for_my_posts', num: cuser.comcoms.count
18 |
19 | %p= link_to t("the_comments.published_comments", num: cuser.published_comcoms_count), [:published, :comments], class: 'btn btn-success btn-sm'
20 | %p= link_to t("the_comments.draft_comments", num: cuser.draft_comcoms_count), [:draft, :comments], class: 'btn btn-info btn-sm'
21 | %p
22 | - if cuser.comments_admin?
23 | = link_to t("the_comments.deleted_comments", num: cuser.deleted_comcoms_count), [:deleted, :comments], class: 'btn btn-default btn-sm'
24 | = link_to t("the_comments.spam_comments", num: cuser.spam_comcoms_count), [:spam, :comments], class: 'btn btn-default btn-sm'
25 | - else
26 | %span.btn.btn-default.btn-sm= t("the_comments.deleted_comments", num: cuser.deleted_comcoms_count)
27 | %span.btn.btn-default.btn-sm= t("the_comments.spam_comments", num: cuser.spam_comcoms_count)
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/_tree.html.haml:
--------------------------------------------------------------------------------
1 | - if commentable.try(:comments_on?) || true
2 | %h4.comments_sum
3 | - if commentable.comments_sum.zero?
4 | Вы можете стать первым, кто оставит комментарий!
5 | - else
6 | Комментариев: #{ commentable.comments_sum }
7 |
8 | - unless current_user
9 | .comments_description
10 | %p — Комментарий можно оставить без регистрации, для этого достаточно заполнить одно обязательное поле Текст комментария. Анонимные комментарии проходят модерацию и до момента одобрения видны только в браузере автора
11 | %p — Комментарии зарегистрированных пользователей публикуются сразу после создания
12 |
13 | .comments#comments
14 | %ol.comments_tree{ data: { comments: { tolarance_time: TheComments.config.tolerance_time } } }
15 | = render partial: 'the_comments/haml/comment', locals: { tree: comments_tree }
16 | = render partial: 'the_comments/haml/form', locals: { commentable: commentable }
17 |
--------------------------------------------------------------------------------
/app/views/the_comments/haml/manage.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :title do
2 | = t "the_comments.management"
3 |
4 | - content_for :comments_sidebar do
5 | = render partial: 'the_comments/haml/sidebar'
6 |
7 | - content_for :comments_main do
8 | = paginate @comments
9 |
10 | - if @comments.blank?
11 | .alert.alert-info= t 'the_comments.no_comments_here'
12 |
13 | .comments
14 | - @comments.each do |comment|
15 | - klass = { published: :success, draft: :info, deleted: :danger }[comment.state.to_sym]
16 | .panel{ class: "panel-#{klass}" }
17 | .panel-heading= comment.header_title
18 | .panel-body
19 | = render partial: 'the_comments/haml/comment_body', locals: { comment: comment }
20 |
21 | - if current_user.comments_admin?
22 | = render partial: 'the_comments/haml/comment_edit', locals: { comment: comment }
23 | = render partial: 'the_comments/haml/manage_controls', locals: { comment: comment }
24 | = render partial: 'the_comments/haml/additional_info', locals: { comment: comment }
25 |
26 | = paginate @comments
27 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_additional_info.html.slim:
--------------------------------------------------------------------------------
1 | .additional_info style: "display:none"
2 | br
3 | table.table.table-striped.table-hover
4 | tr
5 | th Tolerance time:
6 | th IP:
7 | th User Agent:
8 | th Referer:
9 | tr
10 | td= comment.tolerance_time || :none
11 | td= comment.ip
12 | td= comment.user_agent
13 | td= comment.referer
14 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_comment.html.slim:
--------------------------------------------------------------------------------
1 | = build_server_tree(tree, render_module: RenderCommentsTreeHelper, controller: controller)
2 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_comment_body.html.slim:
--------------------------------------------------------------------------------
1 | table.comment_body.table.table-striped.table-hover
2 | tr
3 | td style: "width: 120px;"
4 | b= comment.commentable_type
5 | | →
6 | td
7 | = link_to comment.commentable_title, comment.commentable_url
8 | | (#{comment.try(:commentable_state)})
9 | tr
10 | td
11 | b= t('the_comments.title')
12 | td
13 | - if comment.try(:user)
14 | = link_to comment.user_name, comment.user
15 | - else
16 | = comment.header_title
17 | tr
18 | td
19 | b= t('the_comments.contacts')
20 | td= comment.contacts
21 | tr.success
22 | td
23 | b= t('the_comments.content')
24 | td= comment.content
25 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_comment_edit.html.slim:
--------------------------------------------------------------------------------
1 | .edit_form style: "display:none"
2 | = form_for comment, remote: true, html:{ role: :form } do |f|
3 | table.table.table-striped.table-hover
4 | tr style: "width: 100px;"
5 | td
6 | b= comment.commentable_type
7 | | →
8 | td
9 | = link_to comment.commentable_title, comment.commentable_url
10 | | (#{comment.try(:commentable_state)})
11 | tr
12 | td
13 | b= t('the_comments.title')
14 | td= f.text_field :title, class: "form-control"
15 | tr
16 | td
17 | b= t('the_comments.contacts')
18 | td= f.text_field :contacts, class: "form-control"
19 | tr
20 | td
21 | b= t('the_comments.content')
22 | td= f.text_area :raw_content, class: "form-control", rows: 7
23 | tr
24 | td
25 | td= f.submit t('the_comments.update'), class: "btn btn-success"
26 | hr
27 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_form.html.slim:
--------------------------------------------------------------------------------
1 | h3
2 | = link_to t('the_comments.new'), '#', id: :new_root_comment
3 |
4 | = form_for Comment.new, remote: true, authenticity_token: true do |f|
5 | - if current_user
6 | = render partial: 'the_comments/slim/logined_form', locals: { f: f, commentable: commentable }
7 | - else
8 | = render partial: 'the_comments/slim/guest_form', locals: { f: f, commentable: commentable }
9 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_guest_form.html.slim:
--------------------------------------------------------------------------------
1 | label= t('the_comments.form.title')
2 | p= f.text_field :title, class: 'form-control'
3 |
4 | label= t('the_comments.form.contacts')
5 | p= f.text_field :contacts, class: 'form-control'
6 |
7 | label= t('the_comments.form.content')
8 | p= f.text_area :raw_content, class: 'form-control'
9 |
10 | p.trap
11 | - TheComments.config.empty_inputs.each do |name|
12 | = text_field_tag name, nil, autocomplete: :off, tabindex: -1, id: nil
13 |
14 | = hidden_field_tag :tolerance_time, 0, id: nil, class: :tolerance_time
15 |
16 | = f.hidden_field :commentable_type, value: commentable.class
17 | = f.hidden_field :commentable_id, value: commentable.id
18 | = f.hidden_field :parent_id, class: :parent_id
19 |
20 | p
21 | = f.submit t('the_comments.form.create'), class: :btn
22 | = t('the_comments.form.thank_you')
23 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_logined_form.html.slim:
--------------------------------------------------------------------------------
1 | .logined_comment_form
2 | .user_data
3 | = link_to user_path(current_user), nopin: :nopin do
4 | = image_tag current_user.avatar.url(:thumb)
5 | .comment_data
6 | = hidden_field_tag :tolerance_time, 0, id: nil, class: :tolerance_time
7 | = f.hidden_field :commentable_type, value: commentable.class
8 | = f.hidden_field :commentable_id, value: commentable.id
9 | = f.hidden_field :parent_id, class: :parent_id
10 |
11 | .user_name
12 | b= current_user.username.present? ? current_user.username : current_user.login
13 | label= t('the_comments.form.content')
14 | p= f.text_area :raw_content
15 |
16 | p
17 | = f.submit t('the_comments.form.create'), class: :btn
18 | = t('the_comments.form.thank_you')
19 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_manage_controls.html.slim:
--------------------------------------------------------------------------------
1 | - hidden = "display:none"
2 |
3 | .row.controls
4 | .col-md-9.action_btns
5 | = link_to '#', class: "edit btn btn-success" do
6 | = t('the_comments.edit')
7 |
8 | = link_to '#', class: "btn btn-warning edit", style: "display:none" do
9 | = t('the_comments.cancel')
10 |
11 | - unless to_hide = comment.published? ? hidden : nil
12 | - opts = { remote: true, style: to_hide, method: :post }
13 | = link_to [:to_published, comment], opts.merge(class: "btn btn-primary to_published") do
14 | = t('the_comments.to_published')
15 |
16 | - unless to_hide = comment.draft? ? hidden : nil
17 | - opts = { remote: true, style: to_hide, method: :post }
18 | = link_to [:to_draft, comment], opts.merge(class: "btn btn-warning to_draft") do
19 | = t('the_comments.to_draft')
20 |
21 | - unless to_hide = comment.deleted? ? hidden : nil
22 | - opts = { remote: true, style: to_hide, method: :delete, data: { confirm: t('the_comments.delete_confirm') } }
23 | = link_to [:to_deleted, comment], opts.merge(class: "btn btn-danger to_deleted") do
24 | = t('the_comments.to_deleted')
25 |
26 | - opts = { remote: true, method: :post}
27 | = link_to [:to_spam, comment], opts.merge(class: "btn btn-danger to_spam") do
28 | = t('the_comments.to_spam')
29 | .col-md-3.text-right
30 | = link_to t('the_comments.additional_info'), "#", class: "additional_info btn btn-info"
31 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_sidebar.html.slim:
--------------------------------------------------------------------------------
1 | .panel.panel-default
2 | .panel-heading= t "the_comments.nav.header"
3 | .panel-body
4 | = render partial: 'the_comments/slim/sidebar_backlink'
5 |
6 | - if current_user.comments_admin?
7 | = render partial: 'the_comments/slim/sidebar_admin'
8 | - else
9 | = render partial: 'the_comments/slim/sidebar_user'
10 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_sidebar_admin.html.slim:
--------------------------------------------------------------------------------
1 | - cuser = current_user
2 |
3 | = render partial: 'the_comments/slim/sidebar_user'
4 |
5 | - if cuser.comments_admin?
6 | br
7 | h5= t 'the_comments.in_system', num: Comment.count
8 | p= link_to t("the_comments.published_comments", num: Comment.with_state(:published).count), [:total_published, :comments], class: 'btn btn-success btn-sm'
9 | p= link_to t("the_comments.draft_comments", num: Comment.with_state(:draft).count), [:total_draft, :comments], class: 'btn btn-info btn-sm'
10 | p
11 | = link_to t("the_comments.deleted_comments", num: Comment.with_state(:deleted).count), [:total_deleted, :comments], class: 'btn btn-default btn-sm'
12 | = link_to t("the_comments.spam_comments", num: Comment.where(spam: true).count), [:total_spam, :comments], class: 'btn btn-default btn-sm'
13 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_sidebar_backlink.html.slim:
--------------------------------------------------------------------------------
1 | p= link_to t('the_comments.nav.to_root'), root_path
2 | p= link_to t('the_comments.nav.all_incoming'), manage_comments_url
3 | hr
4 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_sidebar_user.html.slim:
--------------------------------------------------------------------------------
1 | - cuser = current_user
2 |
3 | h5=t 'the_comments.written_by_me', num: cuser.my_comments.count
4 |
5 | p= link_to t("the_comments.published_comments", num: cuser.my_published_comments.count), [:my_published, :comments], class: 'btn btn-success btn-sm'
6 | p= link_to t("the_comments.draft_comments", num: cuser.my_draft_comments.count), [:my_draft, :comments], class: 'btn btn-info btn-sm'
7 |
8 | p
9 | - if cuser.comments_admin?
10 | = link_to t("the_comments.deleted_comments", num: cuser.my_deleted_comments.count), [:my_deleted, :comments], class: 'btn btn-default btn-sm'
11 | = link_to t("the_comments.spam_comments", num: cuser.my_spam_comments.count), [:my_spam, :comments], class: 'btn btn-default btn-sm'
12 | - else
13 | span.btn.btn-default.btn-sm= t("the_comments.deleted_comments", num: cuser.my_deleted_comments.count)
14 | span.btn.btn-default.btn-sm= t("the_comments.spam_comments", num: cuser.my_spam_comments.count)
15 |
16 | br
17 | h5= t 'the_comments.for_my_posts', num: cuser.comcoms.count
18 |
19 | p= link_to t("the_comments.published_comments", num: cuser.published_comcoms_count), [:published, :comments], class: 'btn btn-success btn-sm'
20 | p= link_to t("the_comments.draft_comments", num: cuser.draft_comcoms_count), [:draft, :comments], class: 'btn btn-info btn-sm'
21 | p
22 | - if cuser.comments_admin?
23 | = link_to t("the_comments.deleted_comments", num: cuser.deleted_comcoms_count), [:deleted, :comments], class: 'btn btn-default btn-sm'
24 | = link_to t("the_comments.spam_comments", num: cuser.spam_comcoms_count), [:spam, :comments], class: 'btn btn-default btn-sm'
25 | - else
26 | span.btn.btn-default.btn-sm= t("the_comments.deleted_comments", num: cuser.deleted_comcoms_count)
27 | span.btn.btn-default.btn-sm= t("the_comments.spam_comments", num: cuser.spam_comcoms_count)
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/_tree.html.slim:
--------------------------------------------------------------------------------
1 | - if commentable.comments_on?
2 | h4.comments_sum
3 | - if commentable.comments_sum.zero?
4 | | Вы можете стать первым, кто оставит комментарий!
5 | - else
6 | | Комментариев: #{ commentable.comments_sum }
7 |
8 | - unless current_user
9 | .comments_description
10 | p — Комментарий можно оставить без регистрации, для этого достаточно заполнить одно обязательное поле Текст комментария. Анонимные комментарии проходят модерацию и до момента одобрения видны только в браузере автора
11 | p — Комментарии зарегистрированных пользователей публикуются сразу после создания
12 |
13 | .comments#comments
14 | ol.comments_tree data: { comments: { tolarance_time: TheComments.config.tolerance_time } }
15 | = render partial: 'the_comments/slim/comment', locals: { tree: comments_tree }
16 | = render partial: 'the_comments/slim/form', locals: { commentable: commentable }
17 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/index.html.slim:
--------------------------------------------------------------------------------
1 | - content_for :title do
2 | = t "the_comments.management"
3 |
4 | - content_for :comments_sidebar do
5 | = render partial: 'the_comments/slim/sidebar'
6 |
7 | - content_for :comments_main do
8 | = paginate @comments
9 |
10 | .comments
11 | - @comments.each do |comment|
12 | - klass = { published: :success, draft: :info, deleted: :danger }[comment.state.to_sym]
13 | .panel class: "panel-#{klass}"
14 | .panel-heading= comment.header_title
15 | .panel-body
16 | = render partial: 'the_comments/slim/comment_body', locals: { comment: comment }
17 |
18 | = paginate @comments
19 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/manage.html.slim:
--------------------------------------------------------------------------------
1 | - content_for :title do
2 | = t "the_comments.management"
3 |
4 | - content_for :comments_sidebar do
5 | = render partial: 'the_comments/slim/sidebar'
6 |
7 | - content_for :comments_main do
8 | = paginate @comments
9 |
10 | - if @comments.blank?
11 | .alert.alert-info= t 'the_comments.no_comments_here'
12 |
13 | .comments
14 | - @comments.each do |comment|
15 | - klass = { published: :success, draft: :info, deleted: :danger }[comment.state.to_sym]
16 | .panel class: "panel-#{klass}"
17 | .panel-heading= comment.header_title
18 | .panel-body
19 | = render partial: 'the_comments/slim/comment_body', locals: { comment: comment }
20 |
21 | - if current_user.comments_admin?
22 | = render partial: 'the_comments/slim/comment_edit', locals: { comment: comment }
23 | = render partial: 'the_comments/slim/manage_controls', locals: { comment: comment }
24 | = render partial: 'the_comments/slim/additional_info', locals: { comment: comment }
25 |
26 | = paginate @comments
27 |
--------------------------------------------------------------------------------
/app/views/the_comments/slim/my_comments.html.slim:
--------------------------------------------------------------------------------
1 | - cuser = current_user
2 |
3 | - content_for :title do
4 | = t "the_comments.management"
5 |
6 | - content_for :comments_sidebar do
7 | = render partial: 'the_comments/slim/sidebar'
8 |
9 | - content_for :comments_main do
10 | = paginate @comments
11 |
12 | - if @comments.blank?
13 | .alert.alert-info= t 'the_comments.no_comments_here'
14 |
15 | .comments
16 | - @comments.each do |comment|
17 | - klass = { published: :primary, draft: :warning, deleted: :danger }[comment.state.to_sym]
18 | .panel class: "panel-#{klass}"
19 | .panel-heading= comment.title
20 | .panel-body
21 | = render partial: 'the_comments/slim/comment_body', locals: { comment: comment }
22 |
23 | - if cuser.comments_admin?
24 | = render partial: 'the_comments/slim/comment_edit', locals: { comment: comment }
25 | = render partial: 'the_comments/slim/manage_controls', locals: { comment: comment }
26 | = render partial: 'the_comments/slim/additional_info', locals: { comment: comment }
27 |
28 | = paginate @comments
29 |
--------------------------------------------------------------------------------
/config/initializers/the_comments.rb:
--------------------------------------------------------------------------------
1 | # TheComments.config.param_name => value
2 |
3 | TheComments.configure do |config|
4 | config.max_reply_depth = 3 # comments tree depth
5 | config.tolerance_time = 10 # sec - after this delay user can post a comment
6 | config.default_state = :draft # default state for comment
7 | config.default_owner_state = :published # default state for comment for Moderator
8 | config.empty_inputs = [:commentBody] # array of spam trap fields
9 | config.default_title = 'Undefined title' # default commentable_title for denormalization
10 | config.template_engine = :haml
11 |
12 | config.empty_trap_protection = true
13 | config.tolerance_time_protection = true
14 | end
15 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | activerecord:
3 | attributes:
4 | comment:
5 | raw_content: Content
6 | errors:
7 | models:
8 | comment:
9 | attributes:
10 | raw_content:
11 | blank: 'should not be empty'
12 | # blank: '%{attribute} should not be empty'
13 |
14 | the_comments:
15 | incoming: "Incoming:"
16 | in_system: "In system:"
17 | written_by_me: "Written by me:"
18 | back_to_root: "← To root"
19 |
20 | my_comments: "My comments (%{num})"
21 | draft_comments: "New (%{num})"
22 | published_comments: "Published (%{num})"
23 | deleted_comments: "Deleted (%{num})"
24 | spam_comments: "Spam (%{num})"
25 |
26 | management: Comments management
27 | new: "New comment"
28 | update: "Update comment"
29 | additional_info: "Additional info"
30 | cancel: "Cancel"
31 |
32 | title: "Title on name:"
33 | contacts: "Contacts:"
34 | content: "Message:"
35 |
36 | guest_name: Guest
37 | reply: Reply to this comment
38 | edit: Edit
39 | to_spam: Spam!
40 | to_draft: Draft
41 | to_published: Publicate
42 | to_deleted: Delete
43 |
44 | no_comments_here: No comments here
45 |
46 | nav:
47 | header: "Navigation"
48 |
49 | form:
50 | title: "Your name:"
51 | contacts: "Contacts (only admin can see this):"
52 | content: "Comment* :"
53 | create: "Submit comment"
54 | thank_you: "Thank you!"
55 |
56 | trap: Trap
57 | trap_message: should be empty
58 |
59 | tolerance_time: Page view time
60 | tolerance_time_message: "Please wait %{time} seconds before send a comment and try again"
61 |
62 | delete_confirm: Are you sure?
63 |
64 | cookies: Cookies
65 | cookies_required: 'Please enable cookies and try to reload page'
66 | ajax_requests_required: 'Sorry, JavaScript/Ajax Requests required'
67 | waiting_for_moderation: Waiting for moderation
68 | undefined_commentable: Commentable object is undefined
--------------------------------------------------------------------------------
/config/locales/ru.yml:
--------------------------------------------------------------------------------
1 | ru:
2 | activerecord:
3 | attributes:
4 | comment:
5 | raw_content: Содержимое
6 | errors:
7 | models:
8 | comment:
9 | attributes:
10 | raw_content:
11 | blank: 'Не может быть пустым'
12 |
13 | the_comments:
14 | incoming: "Входящие:"
15 | in_system: "Все в системе: %{num}"
16 | written_by_me: "Написаны мной: %{num}"
17 | for_my_posts: "К моим постам: %{num}"
18 | back_to_root: "← На главную"
19 |
20 | my_comments: "Мои комментарии %{num}"
21 | draft_comments: "На модерации: %{num}"
22 | published_comments: "Опубликованы: %{num}"
23 | deleted_comments: "Удалены: %{num}"
24 | spam_comments: "Спам: %{num}"
25 |
26 | waiting_for_moderation: Комментарий ожидает проверки
27 | management: Управление комментариями
28 | new: "Написать новый комментарий"
29 | update: "Обновить"
30 | additional_info: "Подробнее"
31 | cancel: "Отмена"
32 |
33 | title: "Имя или тема:"
34 | contacts: "Контакты:"
35 | content: "Сообщение:"
36 |
37 | guest_name: Гость
38 | reply: Ответить на этот комментарий
39 | edit: Править
40 | to_spam: Спам!
41 | to_draft: Черновик
42 | to_published: Публиковать
43 | to_deleted: Удалить
44 |
45 | no_comments_here: Здесь нет комментариев
46 |
47 | nav:
48 | header: Комментарии
49 | to_root: На главную
50 | all_incoming: Все входящие
51 |
52 | form:
53 | title: "Ваше имя:"
54 | contacts: "Контакты (не отображаются на сайте):"
55 | content: "Текст комментария* :"
56 | create: "Отправить комментарий"
57 | thank_you: "Спaсибо!"
58 |
59 | trap: Ловушка
60 | trap_message: Должна быть пустой
61 |
62 | tolerance_time: Просмотр времени
63 | tolerance_time_message: "Пожалуйста ожидайте %{time} сек. перед отправкой сообщения"
64 |
65 | cookies: Куки
66 | cookies_required: 'Включите куки и перезагрузите страницу'
67 |
68 | delete_confirm: Вы уверены?
69 | waiting_for_moderation: Ожидает модерации
70 | ajax_requests_required: 'Извините, ожидается JavaScript/Ajax запрос'
71 | undefined_commentable: Комментируемый объект не определен
72 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | class UserRoutes
3 | def call mapper, options = {}
4 | mapper.collection do
5 | mapper.get :manage
6 | mapper.get :my_comments
7 |
8 | mapper.get :my_draft
9 | mapper.get :my_published
10 | mapper.get :my_deleted
11 | mapper.get :my_spam
12 |
13 | mapper.get :draft
14 | mapper.get :published
15 | mapper.get :deleted
16 | mapper.get :spam
17 | end
18 |
19 | mapper.member do
20 | mapper.post :to_spam
21 | mapper.post :to_draft
22 | mapper.post :to_published
23 | mapper.delete :to_deleted
24 | end
25 | end
26 | end
27 |
28 | class AdminRoutes
29 | def call mapper, options = {}
30 | mapper.collection do
31 | mapper.get :total_draft
32 | mapper.get :total_published
33 | mapper.get :total_deleted
34 | mapper.get :total_spam
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/db/migrate/20130101010101_the_comments_change_user.rb:
--------------------------------------------------------------------------------
1 | # null: false => de-facto db-level validation
2 | class TheCommentsChangeUser < ActiveRecord::Migration
3 | def change
4 | change_table :users do |t|
5 | # "Written by me" (cache counters)
6 | t.integer :my_draft_comments_count, default: 0
7 | t.integer :my_published_comments_count, default: 0
8 | t.integer :my_comments_count, default: 0 # my_draft_comments_count + my_published_comments_count
9 |
10 | # commentable's comments => comcoms (cache counters)
11 | # Relation through Comment#holder_id field
12 | t.integer :draft_comcoms_count, default: 0
13 | t.integer :published_comcoms_count, default: 0
14 | t.integer :deleted_comcoms_count, default: 0
15 | t.integer :spam_comcoms_count, default: 0
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/db/migrate/20130101010102_the_comments_create_comments.rb:
--------------------------------------------------------------------------------
1 | class TheCommentsCreateComments < ActiveRecord::Migration
2 | def change
3 | create_table :comments do |t|
4 | # relations
5 | t.integer :user_id
6 | t.integer :holder_id
7 |
8 | # polymorphic, commentable object
9 | t.integer :commentable_id
10 | t.string :commentable_type
11 |
12 | # denormalization
13 | t.string :commentable_url
14 | t.string :commentable_title
15 | t.string :commentable_state
16 |
17 | # comment
18 | t.string :anchor
19 |
20 | t.string :title
21 | t.string :contacts
22 |
23 | t.text :raw_content
24 | t.text :content
25 |
26 | # moderation token
27 | t.string :view_token
28 |
29 | # state machine => :draft | :published | :deleted
30 | t.string :state, default: :draft
31 |
32 | # base user data (BanHammer power)
33 | t.string :ip, default: :undefined
34 | t.string :referer, default: :undefined
35 | t.string :user_agent, default: :undefined
36 | t.integer :tolerance_time
37 |
38 | # unusable: for future versions
39 | t.boolean :spam, default: false
40 |
41 | # nested set
42 | t.integer :parent_id
43 | t.integer :lft
44 | t.integer :rgt
45 | t.integer :depth, default: 0
46 |
47 | t.timestamps
48 | end
49 | end
50 | end
--------------------------------------------------------------------------------
/db/migrate/20130101010103_the_comments_change_commentable.rb:
--------------------------------------------------------------------------------
1 | class TheCommentsChangeCommentable < ActiveRecord::Migration
2 | def change
3 | # Uncomment this. Add fields to Commentable Models
4 | #
5 | # [:users, :posts, :blogs, :articles, :pages].each do |table_name|
6 | # change_table table_name do |t|
7 | # t.integer :draft_comments_count, default: 0
8 | # t.integer :published_comments_count, default: 0
9 | # t.integer :deleted_comments_count, default: 0
10 | # end
11 | # end
12 | end
13 | end
--------------------------------------------------------------------------------
/docs/admin_ui_installation.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## Admin UI installation
4 |
5 | ### 1. Gems install
6 |
7 | **Gemfile**
8 |
9 | ```ruby
10 | # TheComments base
11 | gem 'the_comments', "~> 2.0"
12 |
13 | gem 'haml' # or gem 'slim'
14 | gem 'awesome_nested_set' # or same gem
15 |
16 | # TheComments Admin UI gems
17 |
18 | # pagination
19 | gem 'kaminari'
20 |
21 | # bootstrap 3
22 | gem 'bootstrap-sass', github: 'thomas-mcdonald/bootstrap-sass'
23 | ```
24 |
25 | **Bundle**
26 |
27 | ```
28 | bundle
29 | ```
30 |
31 | ### 2. Assets install
32 |
33 | **app/assets/stylesheets/admin_ui.css**
34 |
35 | ```css
36 | /*
37 | *= require bootstrap
38 | */
39 | ```
40 |
41 | **app/assets/javascripts/admin_ui.js**
42 |
43 | ```js
44 | //= require jquery
45 | //= require jquery_ujs
46 |
47 | //= require bootstrap
48 | //= require the_comments_manage
49 | ```
50 |
51 | ### 3. Admin layout
52 |
53 | You can use following yields to insert TheComments management tools in your Layout.
54 |
55 | ```haml
56 | = yield :comments_sidebar
57 | = yield :comments_main
58 | ```
59 |
60 | For example:
61 |
62 | ```haml
63 | !!! 5
64 | %html(lang="en")
65 | %head
66 | %meta(charset="utf-8")
67 | %meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1")
68 | %meta(name="viewport" content="width=device-width, initial-scale=1.0")
69 | %title= content_for?(:title) ? yield(:title) : "Admin Panel"
70 | %link(href="favicon.ico" rel="shortcut icon")
71 |
72 | = stylesheet_link_tag :admin_ui
73 | = javascript_include_tag :admin_ui
74 | = csrf_meta_tags
75 |
76 | %body
77 | .container
78 | .row
79 | .col-md-12
80 | %h3= content_for?(:title) ? yield(:title) : "Admin Panel"
81 | .row
82 | .col-md-3= yield :comments_sidebar
83 | .col-md-9= yield :comments_main
84 |
85 | = stylesheet_link_tag "//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css"
86 | ```
87 |
88 | ### 4. Comments controller modifications
89 |
90 | by default your comments controller looks like this:
91 |
92 | **app/controllers/comments_controller.rb**
93 |
94 | ```ruby
95 | class CommentsController < ApplicationController
96 | # layout 'admin'
97 |
98 | # Define your restrict methods and use them like this:
99 | #
100 | # before_action :user_required, except: %w[index create]
101 | # before_action :owner_required, except: %w[index create]
102 | # before_action :admin_required, only: %w[total_draft total_published total_deleted total_spam]
103 |
104 | include TheComments::Controller
105 |
106 | # >>> include TheComments::Controller <<<
107 | # (!) Almost all methods based on *current_user* method
108 | #
109 | # 1. Controller's public methods list:
110 | # You can redifine it for your purposes
111 | # public
112 | # %w[ manage index create edit update ]
113 | # %w[ my_comments my_draft my_published ]
114 | # %w[ draft published deleted spam ]
115 | # %w[ to_draft to_published to_deleted to_spam ]
116 | # %w[ total_draft total_published total_deleted total_spam ]
117 | #
118 | #
119 | # 2. Controller's private methods list:
120 | # You can redifine it for your purposes
121 | #
122 | # private
123 | # %w[ comment_template comment_partial ]
124 | # %w[ denormalized_fields request_data_for_comment define_commentable ]
125 | # %w[ comment_params patch_comment_params ]
126 | # %w[ ajax_requests_required cookies_required ]
127 | # %w[ empty_trap_required tolerance_time_required ]
128 |
129 | # KAMINARI pagination:
130 | # following methods based on gem "kaminari"
131 | # You should redefine them if you use something else
132 | #
133 | # public
134 | # %w[ manage index edit ]
135 | # %w[ draft published deleted spam ]
136 | # %w[ my_comments my_draft my_published ]
137 | # %w[ total_draft total_published total_deleted total_spam ]
138 | end
139 | ```
140 |
141 | You must define protection methods to restrict access to Admin UI for regular users.
142 |
143 | ### 5. Visit Admin UI
144 |
145 | **localhost:3000/comments/manage**
146 |
--------------------------------------------------------------------------------
/docs/advanced_installation.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## Advanced Installation
4 |
5 | ### 1. Gems install
6 |
7 | **Gemfile**
8 |
9 | ```ruby
10 | gem 'the_comments', "~> 2.0"
11 |
12 | gem 'haml' # or gem 'slim'
13 | gem 'awesome_nested_set' # or same gem
14 | ```
15 |
16 | **Bundle**
17 |
18 | ```
19 | bundle
20 | ```
21 |
22 | ### 2. Migrations install
23 |
24 | **Copy migrations**
25 |
26 | ```
27 | rake the_comments_engine:install:migrations
28 | ```
29 |
30 | Will create:
31 |
32 | * xxxxx_change_user.rb
33 | * xxxxx_create_comments.rb
34 | * xxxxx_change_commentable.rb
35 |
36 | :warning: **Open and change xxxxx_change_commentable.rb migration**
37 |
38 | ```ruby
39 | class ChangeCommentable < ActiveRecord::Migration
40 | def change
41 | # Additional fields to Commentable Models
42 | # [:posts, :articles, ... ]
43 |
44 | # There is only Post model is commentable
45 | [:posts].each do |table_name|
46 | change_table table_name do |t|
47 | t.integer :draft_comments_count, default: 0
48 | t.integer :published_comments_count, default: 0
49 | t.integer :deleted_comments_count, default: 0
50 | end
51 | end
52 | end
53 | end
54 | ```
55 |
56 | **Invoke migrations**
57 |
58 | ```
59 | rake db:migrate
60 | ```
61 |
62 | ### 3. Code install
63 |
64 | ```ruby
65 | rails g the_comments install
66 | ```
67 |
68 | Will create:
69 |
70 | * config/initializers/the_comments.rb
71 | * app/controllers/comments_controller.rb
72 | * app/models/comment.rb
73 |
74 | :warning: **Open each file and follow an instructions**
75 |
76 | ### 4. Models modifictions
77 |
78 | **app/models/user.rb**
79 |
80 | ```ruby
81 | class User < ActiveRecord::Base
82 | include TheComments::User
83 |
84 | has_many :posts
85 |
86 | # Your way to define privileged users
87 | def admin?
88 | self == User.first
89 | end
90 |
91 | # Required TheComments methods for users restrictions
92 | def comments_admin?
93 | admin?
94 | end
95 |
96 | def comments_moderator? comment
97 | id == comment.holder_id
98 | end
99 | end
100 | ```
101 |
102 | **app/models/post.rb**
103 |
104 | ```ruby
105 | class Post < ActiveRecord::Base
106 | include TheComments::Commentable
107 |
108 | belongs_to :user
109 |
110 | # Denormalization methods
111 | # Migration: t.string :title
112 | # => "My new awesome post"
113 | def commentable_title
114 | try(:title) || "Undefined post title"
115 | end
116 |
117 | # => your way to build URL
118 | # => "/posts/254"
119 | def commentable_url
120 | ['', self.class.to_s.tableize, id].join('/')
121 | end
122 |
123 | # gem 'state_machine'
124 | # Migration: t.string :state
125 | # => "published" | "draft" | "deleted"
126 | def commentable_state
127 | try(:state) || "published"
128 | end
129 | end
130 | ```
131 |
132 | ### 5. Mount Engine routes
133 |
134 | **config/routes.rb**
135 |
136 | ```ruby
137 | MyApp::Application.routes.draw do
138 | root 'posts#index'
139 | resources :posts
140 |
141 | # ...
142 |
143 | # TheComments routes
144 | concern :user_comments, TheComments::UserRoutes.new
145 | concern :admin_comments, TheComments::AdminRoutes.new
146 | resources :comments, concerns: [:user_comments, :admin_comments]
147 | end
148 | ```
149 |
150 | Please, read [documentation](docs/documentation.md) to learn more
151 |
152 | ### 6. Assets install
153 |
154 | **app/assets/stylesheets/application.css**
155 |
156 | ```css
157 | /*
158 | *= require the_comments
159 | */
160 | ```
161 |
162 | **app/assets/javascripts/application.js**
163 |
164 | ```js
165 | //= require the_comments
166 | ```
167 |
168 | ### 7. Controller code example
169 |
170 | **app/controllers/posts_controllers.rb**
171 |
172 | ```ruby
173 | def show
174 | @post = Post.find params[:id]
175 | @comments = @post.comments.with_state([:draft, :published])
176 | end
177 | ```
178 |
179 | ### 8. View code example
180 |
181 | **app/views/posts/show.html.haml**
182 |
183 | ```haml
184 | = render partial: 'the_comments/tree', locals: { commentable: @post, comments_tree: @comments }
185 | ```
186 |
--------------------------------------------------------------------------------
/docs/comment_api.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### Comment API
4 |
5 | ```ruby
6 | @comment = Comment.last
7 |
8 | # Comment creator, can be nil (for Guest)
9 | @comment.user # => User
10 |
11 | # Comment holder
12 | # Owner of commentable object
13 | # shouldn't be nil, should be defined on create
14 | @comment.holder # => User
15 |
16 | # Commentable object
17 | @comment.commentable # => Post
18 |
19 | # Raw user input
20 | @comment.raw_content
21 |
22 | # Processed user input
23 | # method *prepare_content* should be redefined by developer
24 | @comment.content
25 |
26 | # Denormalization fields
27 | @comment.commentable_title # => "Harum sint error odit."
28 | @comment.commentable_url # => "/posts/7"
29 | @comment.commentable_state # => "published"
30 |
31 | # Stat info from request
32 | # Can be used for spam detection
33 | @comment.user_agent # => Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Version/10.10
34 | @comment.tolerance_time # => 5 (secs)
35 | @comment.referer # => localhost:3000/post/7
36 | @comment.ip # => 192.168.0.12
37 |
38 | # State
39 | @comment.state # => draft | published | deleted
40 |
41 | # Spam flag
42 | @comment.spam # => true
43 |
44 | # Alias for *mark_as_spam*
45 | @comment.to_spam
46 |
47 | # mark this comment and all descendants as spam/not spam
48 | @comment.mark_as_spam
49 | @comment.mark_as_not_spam
50 |
51 | # Comment's creator avatar
52 | # this method can be redefined by developer
53 | @comment.avatar_url # => "https://2.gravatar.com/avatar/015e ... 2f05?s=42&d=https://identicons.github.com/AVATAR.png"
54 |
55 | # Anchor of comment
56 | # this method can be redefined by developer
57 | @comment.anchor # => b58020
58 | ```
--------------------------------------------------------------------------------
/docs/commentable_api.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### Commentable API
4 |
5 | ```ruby
6 | class Post < ActiveRecord::Base
7 | include TheCommentsCommentable
8 |
9 | belongs_to :user
10 |
11 | def commentable_title
12 | try(:title) || "Undefined title"
13 | end
14 |
15 | def commentable_url
16 | ['', self.class.to_s.tableize, id].join('/')
17 | end
18 |
19 | def commentable_state
20 | try(:state) || "published"
21 | end
22 | end
23 | ```
24 |
25 | ```ruby
26 | @post = Post.last
27 |
28 | # Post owner
29 | @post.user # => User
30 |
31 | # All comments for commentable object
32 | @post.comments # => ActiveRecord:Collection
33 |
34 | # Cache counters
35 | @post.draft_comments_count # => 1
36 | @post.published_comments_count # => 2
37 | @post.deleted_comments_count # => 0
38 |
39 | # equal values with direct request to database
40 | @post.comments.with_state([:draft]).count # => 1
41 | @post.comments.with_state([:published]).count # => 2
42 | @post.comments.with_state([:deleted]).count # => 0
43 |
44 | # Alias for:
45 | # draft_comments_count + published_comments_count
46 | @post.comments_sum # => 3
47 |
48 | # Spam comments
49 | @post.comments.where(spam: true) # => ActiveRecord::Relation
50 |
51 | # recalculate cache counters
52 | @post.recalculate_comments_counters!
53 |
54 | # Default Denormalization methods
55 | # should be redefined by developer
56 | @post.commentable_title => "Maiores eos rerum numquam aut."
57 | @post.commentable_url => "/posts/9"
58 | @post.commentable_state => "published"
59 | ```
--------------------------------------------------------------------------------
/docs/config_file.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### TheComments config
4 |
5 | Following rails generator will copy default config file into your application
6 |
7 | ```ruby
8 | bundle exec rails g the_comments config
9 | ```
10 |
11 | **config/initializers/the_comments.rb**
12 |
13 | ```ruby
14 | # TheComments.config.param_name => value
15 |
16 | TheComments.configure do |config|
17 | config.max_reply_depth = 3 # comments tree depth
18 | config.tolerance_time = 5 # sec - after this delay user can post a comment
19 | config.default_state = :draft # default state for comment
20 | config.default_owner_state = :published # default state for comment for Moderator
21 | config.empty_inputs = [:commentBody] # array of spam trap fields
22 | config.default_title = 'Undefined title' # default commentable_title for denormalization
23 |
24 | config.empty_trap_protection = true
25 | config.tolerance_time_protection = true
26 | end
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/content_preprocessors.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### Text preprocessors
4 |
5 | TheComments designed for using with text preprocessors: Textile, Markdown, Sanitize, Coderay etc.
6 |
7 | That is why Comment model has 2 fields for user input: **raw_content** and **content**
8 |
9 | ```ruby
10 | class CreateComments < ActiveRecord::Migration
11 | def change
12 | create_table :comments do |t|
13 | # ...
14 |
15 | t.text :raw_content
16 | t.text :content
17 |
18 | # ...
19 | end
20 | end
21 | end
22 | ```
23 |
24 | **raw_content** - field with original user's input
25 |
26 | **content** - field with processed user's input
27 |
28 |
29 |
30 | **before_save :prepare_content** - provides processing of raw user's input
31 |
32 | By default **prepare_content** looks like this:
33 |
34 | ```ruby
35 | def prepare_content
36 | self.content = self.raw_content
37 | end
38 | ```
39 |
40 | I think every developer should redefine this behaviour. To do this you should to use following instructions.
41 |
42 | ### Comment Model customization
43 |
44 | invoke TheComments generator
45 |
46 | ```ruby
47 | bundle exec rails g the_comments models
48 | ```
49 |
50 | This will create **app/models/comment.rb**
51 |
52 | ```ruby
53 | class Comment < ActiveRecord::Base
54 | include TheCommentsBase
55 |
56 | # ---------------------------------------------------
57 | # Define your filters for content
58 | # Expample for: gem 'RedCloth', gem 'sanitize'
59 | # your personal SmilesProcessor
60 |
61 | # def prepare_content
62 | # text = self.raw_content
63 | # text = RedCloth.new(text).to_html
64 | # text = SmilesProcessor.new(text)
65 | # text = Sanitize.clean(text, Sanitize::Config::RELAXED)
66 | # self.content = text
67 | # end
68 | # ---------------------------------------------------
69 | end
70 | ```
71 |
72 | Just redefine **prepare_content** for your purposes
73 |
74 |
--------------------------------------------------------------------------------
/docs/customazation_of_views.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## Customization
4 |
5 | You can use **rails generators** for copy files into your Application. After that you can customize almost everything
6 |
7 | Generators list:
8 |
9 | ```ruby
10 | bundle exec rails g the_comments --help
11 | ```
12 |
13 | ### Customization of views
14 |
15 | Copy View files for customization:
16 |
17 | ```ruby
18 | bundle exec rails g the_comments:views assets
19 | bundle exec rails g the_comments:views views
20 | ```
21 |
22 | ### Customization of comments tree
23 |
24 | Copy Helper file for tree customization:
25 |
26 | ```ruby
27 | bundle exec rails g the_comments:views helper
28 | ```
29 |
30 | For more info read [TheSortableTree doc](https://github.com/the-teacher/the_sortable_tree)
31 |
--------------------------------------------------------------------------------
/docs/denormalization_and_recent_comments.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## Denormalization
4 |
5 | For building of Recent comments list (for polymorphic relationship) we need to have many additional requests to database. It's classic problem of polymorphic comments.
6 |
7 | I use denormalization of commentable objects to solve this problem.
8 |
9 | My practice shows - We need 3 denormalized fields into comment for (request-free) building of recent comments list:
10 |
11 |
12 |
13 | * **Comment#commentable_title** - for example: "My first post about Ruby On Rails"
14 | * **Comment#commentable_url** - for example: "/posts/1-my-first-post-about-ruby-on-rails"
15 | * **Comment#commentable_state** - for example: "draft"
16 |
17 | That is why any **Commentable Model should have few methods** to provide denormalization for Comments.
18 |
19 | ## Recent comments building
20 |
21 | Denormalization makes building of Recent comments (for polymorphic relationship) very easy!
22 |
23 | Controller:
24 |
25 | ```ruby
26 | @comments = Comment.with_state(:published)
27 | .where(commentable_state: [:published])
28 | .order('created_at DESC')
29 | .page(params[:page])
30 | ```
31 |
32 | View:
33 |
34 | ```ruby
35 | - @comments.each do |comment|
36 | %div
37 | %p= comment.commentable_title
38 | %p= link_to comment.commentable_title, comment.commentable_url
39 | %p= comment.content
40 | ```
41 |
--------------------------------------------------------------------------------
/docs/documentation.md:
--------------------------------------------------------------------------------
1 | #### INSTALLATION
2 | * :white_check_mark: [ADVANCED INSTALLATION](advanced_installation.md)
3 | * :white_check_mark: [ADMIN UI INSTALLATION](admin_ui_installation.md)
4 | * :white_check_mark: [Routing](routes.md)
5 | * :white_check_mark: [Generators](generators.md)
6 |
7 | #### API
8 | * :white_check_mark: [User API](user_api.md)
9 | * :white_check_mark: [Comment API](comment_api.md)
10 | * :white_check_mark: [Commentable API](commentable_api.md)
11 |
12 | #### Understanding
13 | * :white_check_mark: [What is ComComs?](what_is_comcoms.md)
14 | * :white_check_mark: [Denormalization and Recent comments](denormalization_and_recent_comments.md)
15 | * :white_check_mark: [What's wrong with other gems?](whats_wrong_with_other_gems.md)
16 | * :white_check_mark: [Why TheComments is better than others gems?](whats_wrong_with_other_gems.md#why-thecomments-is-better-than-others-gems)
17 |
18 | #### Customazation
19 | * :white_check_mark: [Views](customazation_of_views.md)
20 | * :white_check_mark: [Text Preprocessors - Sanitize, Markdown etc.](content_preprocessors.md)
21 |
22 | #### Configuration
23 | * :white_check_mark: [the_comments.rb config file](config_file.md)
24 |
25 | #### Q&A
26 | * :white_check_mark: [I want not to use kaminari for Admin UI](pagination.md)
27 | * :white_check_mark: [Where is example application?](where_is_example_application.md)
28 | * :white_check_mark: [How can I run tests?](where_is_example_application.md#run-tests)
29 |
30 |
--------------------------------------------------------------------------------
/docs/generators.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## Generators
4 |
5 | ```ruby
6 | rails g the_comments NAME
7 | rails g the_comments:views NAME
8 | ```
9 |
10 | #### Migrations
11 |
12 | ```ruby
13 | rake the_comments_engine:install:migrations
14 | ```
15 |
16 | ### Full list
17 |
18 | ```ruby
19 | rails g the_comments --help
20 | ```
21 |
22 | #### Main
23 |
24 | ```ruby
25 | rails g the_comments install
26 | ```
27 |
28 | This will create:
29 |
30 | * config/initializers/the_comments.rb
31 | * app/controllers/comments_controller.rb
32 | * app/models/comment.rb
33 |
34 | #### Controllers
35 |
36 | ```ruby
37 | rails g the_comments controllers
38 | ```
39 |
40 | This will create:
41 |
42 | * app/controllers/comments_controller.rb
43 |
44 | #### Models
45 |
46 | ```ruby
47 | rails g the_comments models
48 | ```
49 |
50 | This will create:
51 |
52 | * app/models/comment.rb
53 |
54 | #### Config
55 |
56 | ```ruby
57 | rails g the_comments config
58 | ```
59 |
60 | #### Locals
61 |
62 | ```ruby
63 | rails g the_comments locales
64 | ```
65 |
66 | #### Views
67 |
68 | ```ruby
69 | rails g the_comments:views js
70 | rails g the_comments:views css
71 | rails g the_comments:views assets
72 | rails g the_comments:views helper
73 | rails g the_comments:views views
74 | ```
75 |
--------------------------------------------------------------------------------
/docs/pagination.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## I want not to use kaminari for Admin UI
4 |
5 | By default we use Kaminari pagination for Admin UI.
6 |
7 | But you can change this. For example, your **comments_controller.rb** looks like this:
8 |
9 | **app/controllers/comments_controller.rb**
10 |
11 | ```ruby
12 | class CommentsController < ApplicationController
13 | # layout 'admin'
14 |
15 | # Define your restrict methods and use them like this:
16 | #
17 | # before_action :user_required, except: %w[index create]
18 | # before_action :owner_required, except: %w[index create]
19 | # before_action :admin_required, only: %w[total_draft total_published total_deleted total_spam]
20 |
21 | include TheComments::Controller
22 |
23 | # >>> include TheComments::Controller <<<
24 | # (!) Almost all methods based on *current_user* method
25 | #
26 | # 1. Controller's public methods list:
27 | # You can redifine it for your purposes
28 | # public
29 | # %w[ manage index create edit update ]
30 | # %w[ my_comments my_draft my_published ]
31 | # %w[ draft published deleted spam ]
32 | # %w[ to_draft to_published to_deleted to_spam ]
33 | # %w[ total_draft total_published total_deleted total_spam ]
34 | #
35 | #
36 | # 2. Controller's private methods list:
37 | # You can redifine it for your purposes
38 | #
39 | # private
40 | # %w[ comment_template comment_partial ]
41 | # %w[ denormalized_fields request_data_for_comment define_commentable ]
42 | # %w[ comment_params patch_comment_params ]
43 | # %w[ ajax_requests_required cookies_required ]
44 | # %w[ empty_trap_required tolerance_time_required ]
45 |
46 | # KAMINARI pagination:
47 | # following methods based on gem "kaminari"
48 | # You should redefine them if you use something else
49 | #
50 | # public
51 | # %w[ manage index edit ]
52 | # %w[ draft published deleted spam ]
53 | # %w[ my_comments my_draft my_published ]
54 | # %w[ total_draft total_published total_deleted total_spam ]
55 | end
56 | ```
57 |
58 | There is we can see comments about kaminari. So, we can try to change it.
59 |
60 | There is example how it can be in your real app:
61 |
62 | ```ruby
63 | class CommentsController < ApplicationController
64 | layout 'admin'
65 |
66 | before_action :user_required, except: %w[index create]
67 | before_action :owner_required, except: %w[index create]
68 | before_action :admin_required, only: %w[total_draft total_published total_deleted total_spam]
69 |
70 | include TheComments::Controller
71 |
72 | public
73 |
74 | def index
75 | @comments = ::Comment.with_state(:published).recent.super_paginator(params)
76 | render comment_template(:index)
77 | end
78 |
79 | def manage
80 | @comments = current_user.comcoms.active.recent.super_paginator(params)
81 | render comment_template(:manage)
82 | end
83 |
84 | def my_comments
85 | @comments = current_user.my_comments.active.recent.super_paginator(params)
86 | render comment_template(:my_comments)
87 | end
88 |
89 | def edit
90 | @comments = current_user.comcoms.where(id: params[:id]).super_paginator(params)
91 | render comment_template(:manage)
92 | end
93 |
94 | %w[draft published deleted].each do |state|
95 | define_method "#{state}" do
96 | @comments = current_user.comcoms.with_state(state).recent.super_paginator(params)
97 | render comment_template(:manage)
98 | end
99 |
100 | define_method "total_#{state}" do
101 | @comments = ::Comment.with_state(state).recent.super_paginator(params)
102 | render comment_template(:manage)
103 | end
104 |
105 | unless state == 'deleted'
106 | define_method "my_#{state}" do
107 | @comments = current_user.my_comments.with_state(state).recent.super_paginator(params)
108 | render comment_template(:my_comments)
109 | end
110 | end
111 | end
112 |
113 | def spam
114 | @comments = current_user.comcoms.where(spam: true).recent.super_paginator(params)
115 | render comment_template(:manage)
116 | end
117 |
118 | def total_spam
119 | @comments = ::Comment.where(spam: true).recent.super_paginator(params)
120 | render comment_template(:manage)
121 | end
122 | end
123 | ```
--------------------------------------------------------------------------------
/docs/routes.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ## TheComments Routes
4 |
5 | **config/routes.rb**
6 |
7 | ```ruby
8 | MyApp::Application.routes.draw do
9 | root 'posts#index'
10 | resources :posts
11 |
12 | # ...
13 |
14 | # TheComments routes
15 | concern :user_comments, TheComments::UserRoutes.new
16 | concern :admin_comments, TheComments::AdminRoutes.new
17 | resources :comments, concerns: [:user_comments, :admin_comments]
18 | end
19 | ```
20 |
21 | And after that you can see routes:
22 |
23 | ```ruby
24 | rake routes | grep comments
25 | ```
26 |
27 | ```ruby
28 | comments / TheComments::Engine
29 | to_spam_comment POST /comments/:id/to_spam(.:format) comments#to_spam
30 | to_draft_comment POST /comments/:id/to_draft(.:format) comments#to_draft
31 | to_published_comment POST /comments/:id/to_published(.:format) comments#to_published
32 | to_deleted_comment DELETE /comments/:id/to_deleted(.:format) comments#to_deleted
33 | manage_comments GET /comments/manage(.:format) comments#manage
34 | my_draft_comments GET /comments/my_draft(.:format) comments#my_draft
35 | my_published_comments GET /comments/my_published(.:format) comments#my_published
36 | my_comments_comments GET /comments/my_comments(.:format) comments#my_comments
37 | total_draft_comments GET /comments/total_draft(.:format) comments#total_draft
38 | total_published_comments GET /comments/total_published(.:format) comments#total_published
39 | total_deleted_comments GET /comments/total_deleted(.:format) comments#total_deleted
40 | total_spam_comments GET /comments/total_spam(.:format) comments#total_spam
41 | draft_comments GET /comments/draft(.:format) comments#draft
42 | published_comments GET /comments/published(.:format) comments#published
43 | deleted_comments GET /comments/deleted(.:format) comments#deleted
44 | spam_comments GET /comments/spam(.:format) comments#spam
45 | comments GET /comments(.:format) comments#index
46 | POST /comments(.:format) comments#create
47 | new_comment GET /comments/new(.:format) comments#new
48 | edit_comment GET /comments/:id/edit(.:format) comments#edit
49 | comment GET /comments/:id(.:format) comments#show
50 | PATCH /comments/:id(.:format) comments#update
51 | PUT /comments/:id(.:format) comments#update
52 | DELETE /comments/:id(.:format) comments#destroy
53 | ```
54 |
55 | And now you can use url helpers with 2 ways:
56 |
57 | ### Way 1. Url Helpers
58 |
59 | ```ruby
60 | = link_to 'link', comments_path
61 | = link_to 'link', manage_comments_path
62 | = link_to 'link', new_comment_path
63 |
64 | = link_to 'link', comment_path(@comment)
65 | = link_to 'link', to_spam_comment_path(@comment)
66 | ```
67 |
68 | ### Way 2. Array notation
69 |
70 | ```ruby
71 | = link_to 'link', [:index, :comments]
72 | = link_to 'link', [:manage, :comments]
73 | = link_to 'link', [:draft, :comments]
74 |
75 | = link_to 'link', [@comment]
76 | = link_to 'link', [:to_spam, @comment]
77 | ```
--------------------------------------------------------------------------------
/docs/screencast.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/screencast.jpg
--------------------------------------------------------------------------------
/docs/the_comments.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/the_comments.jpg
--------------------------------------------------------------------------------
/docs/the_comments_view_1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/the_comments_view_1.gif
--------------------------------------------------------------------------------
/docs/the_comments_view_2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/the_comments_view_2.gif
--------------------------------------------------------------------------------
/docs/the_comments_view_3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/the_comments_view_3.gif
--------------------------------------------------------------------------------
/docs/the_comments_view_4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/the_comments_view_4.gif
--------------------------------------------------------------------------------
/docs/the_comments_view_5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/docs/the_comments_view_5.gif
--------------------------------------------------------------------------------
/docs/user_api.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### User API
4 |
5 | **When User is not commentable model**
6 |
7 | ```ruby
8 | class User < ActiveRecord::Base
9 | include TheCommentsUser
10 |
11 | has_many :posts # commentable model
12 | has_many :products # commentable model
13 | end
14 | ```
15 |
16 | :warning: Please, read this: [What is ComComs?](what_is_comcoms.md)
17 |
18 |
19 | We can use following methods
20 |
21 | ```ruby
22 | @user = User.first
23 |
24 | @user.comcoms #=> all comments for posts and products, where user is owner
25 |
26 | # cache counters
27 | @user.draft_comcoms_count # => 1
28 | @user.published_comcoms_count # => 5
29 | @user.deleted_comcoms_count # => 3
30 | @user.spam_comcoms_count # => 2
31 |
32 | # equal values, but with request to database
33 | @user.comcoms.with_state([:draft]).count # => 1
34 | @user.comcoms.with_state([:published]).count # => 5
35 | @user.comcoms.with_state([:deleted]).count # => 3
36 | @user.comcoms.where(spam: true).count # => 2
37 |
38 | # draft and published comments
39 | # written by this user
40 | @user.my_comments # => ActiveRecord::Relation
41 |
42 | # cache counters for comments
43 | # written by this user
44 | # there is no cache counter for deleted state!
45 | @user.my_draft_comments_count # => 3
46 | @user.my_published_comments_count # => 7
47 |
48 | # equal values, but with request to database
49 | @user.my_draft_comments.count # => 3
50 | @user.my_published_comments.count # => 7
51 | @user.my_deleted_comments.count # => 1
52 |
53 | # helper methods to get comments
54 | # written by this user
55 | @user.my_draft_comments # => ActiveRecord::Relation
56 | @user.my_published_comments # => ActiveRecord::Relation
57 | @user.my_deleted_comments # => ActiveRecord::Relation
58 |
59 | # recalculate cache counters
60 | @user.recalculate_my_comments_counter!
61 | ```
62 |
63 | **When User is commentable model**
64 |
65 | ```ruby
66 | class User < ActiveRecord::Base
67 | include TheCommentsUser
68 | include TheCommentsCommentable
69 |
70 | has_many :posts # commentable model
71 | has_many :products # commentable model
72 | end
73 | ```
74 |
75 | you should to use following instruction [Commentable API](commentable_api.md)
76 |
--------------------------------------------------------------------------------
/docs/what_is_comcoms.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### What is ComComs?
4 |
5 | :warning: **comcoms** - is main method to get all comments related with user's commentable models.
6 |
7 | :warning: **comments** - is main method to get comments related with any commentable model.
8 |
9 | **ComComs** - **com**ments of **com**mentable models
10 |
11 | ComComs are all incoming comments for all models, where this user is owner.
12 |
13 | For example, some user **has_many :posts**, and **has_many :products** - all comments for all user's posts and all user's products called as **comcoms**.
14 |
15 | #### Why we need ComComs?
16 |
17 | User model can be commentable too. For example to build user's "public wall" (like tweets list for current user).
18 |
19 | And we should to separate **comments** attached to user model (tweets) and comments attached to any another user's model.
20 |
21 | That is why User model in-fact has following relationship declarations:
22 |
23 | ```ruby
24 | class User < ActiveRecord::Base
25 | has_many :comcoms, class_name: :Comment, foreign_key: :holder_id
26 |
27 | # and if User model is commentable model
28 | # has_many :comments, as: :commentable
29 |
30 | has_many :posts
31 | has_many :products
32 | end
33 | ```
34 |
35 | in real application it should be described like this:
36 |
37 | ```ruby
38 | class User < ActiveRecord::Base
39 | include TheCommentsUser
40 | include TheCommentsCommentable
41 |
42 | has_many :posts
43 | has_many :products
44 | end
45 | ```
46 |
47 | But in most popular situation User model should not be commentable, and you should use only **comcoms** method to get all comments related with this user:
48 |
49 | ```ruby
50 | class User < ActiveRecord::Base
51 | include TheCommentsUser
52 |
53 | has_many :posts
54 | has_many :products
55 | end
56 | ```
57 |
58 | and later in your application
59 |
60 | ```ruby
61 | @user = User.find params[:id]
62 | @user.comcoms.count # => 42
63 | ```
64 |
--------------------------------------------------------------------------------
/docs/whats_wrong_with_other_gems.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### What's wrong with other gems?
4 |
5 | Take a look at [Ruby-Toolbox](https://www.ruby-toolbox.com/categories/rails_comments). What can we see?
6 |
7 | * [Acts as commentable with threading](https://github.com/elight/acts_as_commentable_with_threading) - Where is the render helper for the tree? There is no helper! Am I supposed to write a render helper for the tree myself? Nooooo!!! I'm sorry, I can't use this gem.
8 | * [acts_as_commentable](https://github.com/jackdempsey/acts_as_commentable) - I can see the code for models. But I can't see the code for controllers and views. Unfortunately, there is no threading. This isn't enough for me.
9 | * [opinio](https://github.com/Draiken/opinio) - Better, but still no threading. I can do better!
10 | * [has_threaded_comments](https://github.com/aarongough/has_threaded_comments) - A solid gem. Has model, controller and view helpers for tree rendering! **But** last activity was 2 years ago, it still needs a few features - I can do better.
11 |
12 | ### Why is TheComments better than other gems?
13 |
14 | 1. TheComments allows for threaded comments
15 | 2. **Only TheComments has special helper for tree rendering** (based on [TheSortableTree](https://github.com/the-teacher/the_sortable_tree)).
16 | 3. TheComments is designed to reduce database requests. Helpful for cache counters.
17 | 4. TheComments has a solution for [building Recent Comments](https://github.com/the-teacher/the_comments/blob/master/docs/denormalization_and_recent_comments.md) (for polymorphic relations)
18 | 5. TheComments is designed for text preprocessors (Textile, Markdown, Sanitize, Coderay etc.)
19 | 6. TheComments has an admin UI based on bootstrap 3
20 | 7. TheComments is an "all-in-one" solution.
21 | It has: Models and Controllers logic (via concerns), Generators, Views, Helper for fast Tree rendering and Admin UI.
22 | 8. If you have problems with TheComments, I'll try to help you via skype: **ilya.killich**
23 |
24 | ### TheComments based on:
25 |
26 | 1. [AwesomeNestedSet](https://github.com/collectiveidea/awesome_nested_set) - for comments threading
27 | 2. [TheSortableTree](https://github.com/the-teacher/the_sortable_tree) - for fast rendering of comments tree
28 | 3. [State Machine](https://github.com/pluginaweek/state_machine) - to provide easy and correct recalculation cache counters on states transitions
--------------------------------------------------------------------------------
/docs/where_is_example_application.md:
--------------------------------------------------------------------------------
1 | ← [documentation](documentation.md)
2 |
3 | ### Dummy Application
4 |
5 | TheComments repository contains a dummy application for development and testing.
6 |
7 | It's here: [Dummy App](https://github.com/the-teacher/the_comments/tree/master/spec/dummy_app)
8 |
9 | To run the dummy app:
10 |
11 | ```ruby
12 | git clone https://github.com/the-teacher/the_comments.git
13 |
14 | cd the_comments/spec/dummy_app/
15 |
16 | bundle
17 |
18 | rake db:bootstrap_and_seed
19 |
20 | rails s -p 3000 -b localhost
21 | ```
22 |
23 | ### Run tests
24 |
25 | To run the RSPEC tests:
26 |
27 | ```ruby
28 | git clone https://github.com/the-teacher/the_comments.git
29 |
30 | cd the_comments/spec/dummy_app/
31 |
32 | bundle
33 |
34 | rake db:bootstrap RAILS_ENV=test
35 |
36 | rspec --format documentation
37 | ```
38 |
--------------------------------------------------------------------------------
/gem_version.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | VERSION = "2.3.1"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/generators/the_comments/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | TheComments: generators will copy files for organize comments tree in your web project
3 |
4 | Usage: [bundle exec] rails g the_comments NAME
5 | Usage: [bundle exec] rails g the_comments:views NAME
6 |
7 | # This text:
8 | > rails g the_comments --help
9 |
10 | # Main generators:
11 | > rails g the_comments install
12 |
13 | This will create:
14 | config/initializers/the_comments.rb
15 | app/controllers/comments_controller.rb
16 | app/models/comment.rb
17 |
18 | # Controller generators:
19 | > rails g the_comments controllers
20 |
21 | This will create:
22 | app/controllers/comments_controller.rb
23 |
24 | # Model generators:
25 | > rails g the_comments models
26 |
27 | This will create:
28 | app/models/comment.rb
29 |
30 | # Config generators:
31 | > rails g the_comments config
32 |
33 | # Locals generators:
34 | > rails g the_comments locales
35 |
36 | # View Generators:
37 | > rails g the_comments:views js
38 | > rails g the_comments:views css
39 | > rails g the_comments:views assets
40 | > rails g the_comments:views helper
41 | > rails g the_comments:views views
42 |
43 | # Migrations:
44 | > rake the_comments_engine:install:migrations
--------------------------------------------------------------------------------
/lib/generators/the_comments/the_comments_generator.rb:
--------------------------------------------------------------------------------
1 | class TheCommentsGenerator < Rails::Generators::NamedBase
2 | source_root File.expand_path('../templates', __FILE__)
3 | # argument :xname, type: :string, default: :xname
4 |
5 | # > rails g the_comments NAME
6 | def generate_controllers
7 | case gen_name
8 | when 'locales'
9 | cp_locales
10 | when 'models'
11 | cp_models
12 | when 'controllers'
13 | cp_controllers
14 | when 'config'
15 | cp_config
16 | when 'install'
17 | cp_config
18 | cp_models
19 | cp_controllers
20 | else
21 | puts 'TheComments Generator - wrong Name'
22 | puts 'Try to use [ install | config | controllers | models ]'
23 | end
24 | end
25 |
26 | private
27 |
28 | def root_path; TheComments::Engine.root; end
29 |
30 | def gen_name
31 | name.to_s.downcase
32 | end
33 |
34 | def cp_config
35 | copy_file "#{root_path}/config/initializers/the_comments.rb",
36 | "config/initializers/the_comments.rb"
37 | end
38 |
39 | def cp_models
40 | copy_file "#{root_path}/app/models/_templates_/comment.rb",
41 | "app/models/comment.rb"
42 | end
43 |
44 | def cp_controllers
45 | copy_file "#{root_path}/app/controllers/_templates_/comments_controller.rb",
46 | "app/controllers/comments_controller.rb"
47 | end
48 |
49 | def cp_locales
50 | copy_file "#{root_path}/config/locales/en.yml",
51 | "config/locales/en.the_comments.yml"
52 |
53 | copy_file "#{root_path}/config/locales/ru.yml",
54 | "config/locales/ru.the_comments.yml"
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/generators/the_comments/views_generator.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | module Generators
3 | class ViewsGenerator < Rails::Generators::NamedBase
4 | source_root TheComments::Engine.root
5 |
6 | def self.banner
7 | <<-BANNER.chomp
8 |
9 | USAGE: [bundle exec] rails g the_comments:views NAME
10 |
11 | > rails g the_comments:views js
12 | > rails g the_comments:views css
13 | > rails g the_comments:views assets
14 |
15 | > rails g the_comments:views views
16 | > rails g the_comments:views helper
17 |
18 | > rails g the_comments:views all
19 |
20 | BANNER
21 | end
22 |
23 | def copy_sortable_tree_files
24 | copy_gem_files
25 | end
26 |
27 | private
28 |
29 | def param_name
30 | name.downcase
31 | end
32 |
33 | def copy_gem_files
34 | case param_name
35 | when 'js'
36 | js_copy
37 | when 'css'
38 | css_copy
39 | when 'assets'
40 | js_copy; css_copy
41 | when 'views'
42 | views_copy
43 | when 'helper'
44 | helper_copy
45 | when 'all'
46 | js_copy
47 | css_copy
48 | views_copy
49 | helper_copy
50 | else
51 | puts 'TheComments View Generator - wrong Name'
52 | puts "Wrong params - use only [ js | css | assets | views | helper | all ] values"
53 | end
54 | end
55 |
56 | def js_copy
57 | f1 = "app/assets/javascripts/the_comments.js.coffee"
58 | f2 = "app/assets/javascripts/the_comments_manage.js.coffee"
59 | copy_file f1, f1
60 | copy_file f2, f2
61 | end
62 |
63 | def css_copy
64 | f1 = "app/assets/stylesheets/the_comments.css.scss"
65 | copy_file f1, f1
66 | end
67 |
68 | def views_copy
69 | d1 = "app/views/the_comments"
70 | directory d1, d1
71 | end
72 |
73 | def helper_copy
74 | f1 = "app/helpers/render_comments_tree_helper.rb"
75 | copy_file f1, f1
76 | end
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/lib/the_comments.rb:
--------------------------------------------------------------------------------
1 | require 'state_machine'
2 | require 'state_machine/version'
3 |
4 | require 'the_simple_sort'
5 | require 'the_sortable_tree'
6 |
7 | require 'the_comments/config'
8 | require 'the_comments/version'
9 |
10 | module TheComments
11 | COMMENTS_COOKIES_TOKEN = 'JustTheCommentsCookies'
12 |
13 | class Engine < Rails::Engine
14 | config.autoload_paths += Dir["#{config.root}/app/controllers/concerns/**/"]
15 | config.autoload_paths += Dir["#{config.root}/app/models/concerns/**/"]
16 | end
17 | end
18 |
19 | # Loading of concerns
20 | _root_ = File.expand_path('../../', __FILE__)
21 | require "#{_root_}/config/routes.rb"
22 |
23 | if StateMachine::VERSION.to_f <= 1.2
24 | puts '~' * 50
25 | puts 'TheComments'
26 | puts '~' * 50
27 | puts 'WARNING!'
28 | puts 'StateMachine patch for Rails4 will be applied'
29 | puts
30 | puts '> private method *around_validation* from StateMachine::Integrations::ActiveModel will be public'
31 | puts
32 | puts 'https://github.com/pluginaweek/state_machine/issues/295'
33 | puts 'https://github.com/pluginaweek/state_machine/issues/251'
34 | puts '~' * 50
35 | module StateMachine::Integrations::ActiveModel
36 | public :around_validation
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/the_comments/config.rb:
--------------------------------------------------------------------------------
1 | module TheComments
2 | def self.configure(&block)
3 | yield @config ||= TheComments::Configuration.new
4 | end
5 |
6 | def self.config
7 | @config
8 | end
9 |
10 | # Configuration class
11 | class Configuration
12 | include ActiveSupport::Configurable
13 |
14 | config_accessor :max_reply_depth,
15 | :tolerance_time,
16 | :default_state,
17 | :default_owner_state,
18 | :empty_inputs,
19 | :default_title,
20 | :template_engine,
21 | :empty_trap_protection,
22 | :tolerance_time_protection
23 | end
24 |
25 | configure do |config|
26 | config.max_reply_depth = 3
27 | config.tolerance_time = 5
28 | config.default_state = :draft
29 | config.default_owner_state = :published
30 | config.empty_inputs = [:message]
31 | config.default_title = 'Undefined title'
32 | config.template_engine = :haml
33 |
34 | config.empty_trap_protection = true
35 | config.tolerance_time_protection = true
36 | end
37 | end
--------------------------------------------------------------------------------
/lib/the_comments/version.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../../../gem_version', __FILE__)
--------------------------------------------------------------------------------
/spec/dummy_app/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 | .rvmrc
10 |
11 | # Ignore the default SQLite database.
12 | /db/*.db
13 | /db/*.sqlite3
14 | /db/*.sqlite3-journal
15 |
16 | # Ignore all logfiles and tempfiles.
17 | /log/*.log
18 | /tmp
19 |
--------------------------------------------------------------------------------
/spec/dummy_app/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/spec/dummy_app/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Base gems
4 | gem 'rails', '4.1.4 ' #github: 'rails/rails', branch: 'master'
5 | gem 'sqlite3'
6 |
7 | gem 'jquery-rails'
8 | gem 'jbuilder', '~> 1.2'
9 |
10 | gem 'uglifier', '>= 1.3.0'
11 | gem 'sass-rails', '~> 4.0.0'
12 | gem 'coffee-rails', '~> 4.0.0'
13 |
14 | gem 'thin', group: [:development]
15 | # gem 'therubyracer', platforms: :ruby
16 | gem 'sdoc', require: false, group: [:doc]
17 |
18 | # App gems
19 | gem 'haml'
20 | gem 'sorcery'
21 | gem 'kaminari'
22 | gem 'haml-rails'
23 |
24 | # TheComments requires
25 | gem 'the_comments',
26 | path: '../../'
27 |
28 | gem 'bootstrap-sass',
29 | github: 'thomas-mcdonald/bootstrap-sass'
30 |
31 | gem 'awesome_nested_set',
32 | git: 'https://github.com/collectiveidea/awesome_nested_set.git',
33 | branch: 'master'
34 |
35 | # Test gems
36 | group :development, :test do
37 | gem 'faker'
38 | gem 'factory_girl'
39 | gem "factory_girl_rails"
40 | gem 'rspec-rails', '~> 2.0'
41 |
42 | # gem 'database_cleaner'
43 | end
44 |
--------------------------------------------------------------------------------
/spec/dummy_app/README.md:
--------------------------------------------------------------------------------
1 | ## TheComments Dummy App
2 |
3 | ### First step
4 |
5 | ```
6 | git clone https://github.com/the-teacher/the_comments.git
7 |
8 | cd the_comments/spec/dummy_app/
9 |
10 | bundle
11 | ```
12 |
13 | ### App start
14 |
15 | ```
16 | rake db:bootstrap_and_seed
17 |
18 | rails s -p 3000 -b localhost
19 | ```
20 |
21 | Browser
22 |
23 | ```
24 | http://localhost:3000/
25 | ```
26 |
27 | ### Tests start
28 |
29 | ```
30 | rake db:bootstrap RAILS_ENV=test
31 |
32 | rspec --format documentation
33 | ```
34 |
--------------------------------------------------------------------------------
/spec/dummy_app/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | App::Application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/app/assets/javascripts/admin_panel.js:
--------------------------------------------------------------------------------
1 | //= require jquery
2 | //= require jquery_ujs
3 |
4 | //= require bootstrap
5 | //= require the_comments_manage
--------------------------------------------------------------------------------
/spec/dummy_app/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 |
16 | //= require the_comments
--------------------------------------------------------------------------------
/spec/dummy_app/app/assets/stylesheets/admin_panel.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require bootstrap
3 | */
--------------------------------------------------------------------------------
/spec/dummy_app/app/assets/stylesheets/app.css.scss:
--------------------------------------------------------------------------------
1 | .body{
2 | margin: auto;
3 | width: 800px;
4 | }
--------------------------------------------------------------------------------
/spec/dummy_app/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the top of the
9 | * compiled file, but it's generally better to create a new file per style scope.
10 | *
11 |
12 | *= require_self
13 | *= require app
14 | *= require the_comments
15 |
16 | */
17 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | include TheComments::ViewToken
3 |
4 | # Prevent CSRF attacks by raising an exception.
5 | # For APIs, you may want to use :null_session instead.
6 | protect_from_forgery with: :exception
7 | end
8 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/controllers/comments_controller.rb:
--------------------------------------------------------------------------------
1 | class CommentsController < ApplicationController
2 | # Define your restrict methods and use them like this:
3 | #
4 | # before_action :user_required, except: [:index, :create]
5 | #
6 | # before_action :owner_required, only: [:my, :incoming, :edit, :trash]
7 | # before_action :moderator_required, only: [:update, :to_published, :to_draft, :to_spam, :to_trash]
8 |
9 | layout 'admin'
10 |
11 | include TheComments::Controller
12 |
13 | # Public methods:
14 | #
15 | # [:index, :create]
16 |
17 | # Application side methods:
18 | # Overwrite following default methods if it's need
19 | # Following methods based on *current_user* helper method
20 | # Look here: https://github.com/the-teacher/the_comments/blob/master/app/controllers/concerns/the_comments_controller.rb#L62
21 | #
22 | # [:my, :incoming, :edit, :trash]
23 |
24 | # You must protect following methods
25 | # Only comments moderator (holder or admin) can invoke following actions
26 | #
27 | # [:update, :to_published, :to_draft, :to_spam, :to_trash]
28 | end
--------------------------------------------------------------------------------
/spec/dummy_app/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/app/controllers/posts_controller.rb:
--------------------------------------------------------------------------------
1 | class PostsController < ApplicationController
2 | def index
3 | @posts = Post.all
4 | @recent_comments = Comment.with_state(:published)
5 | .where(commentable_state: [:published])
6 | .recent.page(params[:page])
7 | end
8 |
9 | def show
10 | @post = Post.find params[:id]
11 | @comments = @post.comments.with_state([:draft, :published]).nested_set
12 | end
13 | end
--------------------------------------------------------------------------------
/spec/dummy_app/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 | def autologin
3 | user = User.find params[:id]
4 | auto_login user if user
5 | redirect_to request.referrer || root_path
6 | end
7 | end
--------------------------------------------------------------------------------
/spec/dummy_app/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/app/mailers/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/app/models/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/app/models/comment.rb:
--------------------------------------------------------------------------------
1 | class Comment < ActiveRecord::Base
2 | include TheComments::Comment
3 | # ---------------------------------------------------
4 | # Define comment's avatar url
5 | # Usually we use Comment#user (owner of comment) to define avatar
6 | # @blog.comments.includes(:user) <= use includes(:user) to decrease queries count
7 | # comment#user.avatar_url
8 | # ---------------------------------------------------
9 |
10 | # ---------------------------------------------------
11 | # Simple way to define avatar url
12 |
13 | # def avatar_url
14 | # hash = Digest::MD5.hexdigest self.id.to_s
15 | # "http://www.gravatar.com/avatar/#{hash}?s=30&d=identicon"
16 | # end
17 | # ---------------------------------------------------
18 |
19 | # ---------------------------------------------------
20 | # Define your filters for content
21 | # Expample for: gem 'RedCloth', gem 'sanitize'
22 | # your personal SmilesProcessor
23 |
24 | # def prepare_content
25 | # text = self.raw_content
26 | # text = RedCloth.new(text).to_html
27 | # text = SmilesProcessor.new(text)
28 | # text = Sanitize.clean(text, Sanitize::Config::RELAXED)
29 | # self.content = text
30 | # end
31 | # ---------------------------------------------------
32 | end
--------------------------------------------------------------------------------
/spec/dummy_app/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/app/models/post.rb:
--------------------------------------------------------------------------------
1 | class Post < ActiveRecord::Base
2 | include TheComments::Commentable
3 |
4 | belongs_to :user
5 |
6 | def commentable_title
7 | title
8 | end
9 |
10 | def commentable_url
11 | ['', self.class.to_s.tableize, id].join('/')
12 | end
13 |
14 | def commentable_state
15 | :published.to_s
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | include TheComments::User
3 | include TheComments::Commentable
4 |
5 | authenticates_with_sorcery!
6 |
7 | has_many :posts
8 |
9 | # can be replaced to TheCommentsUser as default
10 | def admin?
11 | self == User.first
12 | end
13 |
14 | def comments_admin?
15 | admin?
16 | end
17 |
18 | def comments_moderator? comment
19 | id == comment.holder_id
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/views/layouts/admin.html.haml:
--------------------------------------------------------------------------------
1 | !!! 5
2 | %html(lang="ru")
3 | %head
4 | %meta(charset="utf-8")
5 | %meta(http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1")
6 | %meta(name="viewport" content="width=device-width, initial-scale=1.0")
7 | %title= content_for?(:title) ? yield(:title) : "Admin Panel"
8 | %link(href="favicon.ico" rel="shortcut icon")
9 |
10 | = stylesheet_link_tag :admin_panel
11 | = javascript_include_tag :admin_panel
12 | = csrf_meta_tags
13 |
14 | %body
15 | .container
16 | .row
17 | .col-md-12
18 | %h3= content_for?(:title) ? yield(:title) : "Admin Panel"
19 | .row
20 | .col-md-3
21 | = yield :comments_sidebar
22 | .col-md-9
23 | = yield :comments_main
24 |
25 | = stylesheet_link_tag "//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css"
--------------------------------------------------------------------------------
/spec/dummy_app/app/views/layouts/application.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html
3 | %head
4 | %title TheComments Dummy App
5 | = stylesheet_link_tag :application, media: :all
6 | = javascript_include_tag :application
7 | = csrf_meta_tags
8 |
9 | %body
10 | .body
11 | %p
12 | = link_to 'Home', root_path
13 | \|
14 | - if current_user
15 | User: #{current_user.username}
16 | \|
17 | = link_to "Comments Manage (+#{current_user.draft_comcoms_count})", manage_comments_path
18 | - else
19 | Guest
20 | = yield
--------------------------------------------------------------------------------
/spec/dummy_app/app/views/posts/index.html.haml:
--------------------------------------------------------------------------------
1 | %h3 Posts:
2 | %ul
3 | - @posts.each do |post|
4 | %li
5 | = link_to post_url(post) do
6 | = "#{post.title} (#{post.comments_sum})"
7 |
8 | %h3 Autologin Users:
9 | %ul
10 | - User.all.each do |user|
11 | %li
12 | = link_to autologin_url(user) do
13 | - admin = user.comments_admin? ? '[Admin]' : nil
14 | = "#{user.username} (#{user.draft_comcoms_count}) #{admin}"
15 |
16 | %h3 Recent Comments:
17 | %ul
18 | - @recent_comments.each do |comment|
19 | %p
20 | %b= link_to comment.commentable_title, comment.commentable_url
21 | %br
22 | %i= comment.content
23 |
--------------------------------------------------------------------------------
/spec/dummy_app/app/views/posts/show.html.haml:
--------------------------------------------------------------------------------
1 | %h3= @post.title
2 | %p= @post.content
3 |
4 | %p
5 | Comments count: #{@post.comments_sum}
6 |
7 | = render partial: 'the_comments/tree', locals: { commentable: @post, comments_tree: @comments }
--------------------------------------------------------------------------------
/spec/dummy_app/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy_app/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy_app/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy_app/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(:default, Rails.env)
8 |
9 | module App
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 |
15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
17 | # config.time_zone = 'Central Time (US & Canada)'
18 |
19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
21 | # config.i18n.default_locale = :de
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
5 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/database.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: sqlite3
3 | database: db/development.db
4 | pool: 5
5 | timeout: 5000
6 |
7 | test:
8 | adapter: sqlite3
9 | database: db/test.db
10 | pool: 5
11 | timeout: 5000
--------------------------------------------------------------------------------
/spec/dummy_app/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | App::Application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | App::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 | end
30 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | App::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both thread web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Generate digests for assets URLs.
33 | config.assets.digest = true
34 |
35 | # Version of your assets, change this if you want to expire all your assets.
36 | config.assets.version = '1.0'
37 |
38 | # Specifies the header that your server uses for sending files.
39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
41 |
42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
43 | # config.force_ssl = true
44 |
45 | # Set to :debug to see everything in the log.
46 | config.log_level = :info
47 |
48 | # Prepend all log lines with the following tags.
49 | # config.log_tags = [ :subdomain, :uuid ]
50 |
51 | # Use a different logger for distributed setups.
52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
53 |
54 | # Use a different cache store in production.
55 | # config.cache_store = :mem_cache_store
56 |
57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
58 | # config.action_controller.asset_host = "http://assets.example.com"
59 |
60 | # Precompile additional assets.
61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
62 | # config.assets.precompile += %w( search.js )
63 |
64 | # Ignore bad email addresses and do not raise email delivery errors.
65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
66 | # config.action_mailer.raise_delivery_errors = false
67 |
68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
69 | # the I18n.default_locale when a translation can not be found).
70 | config.i18n.fallbacks = true
71 |
72 | # Send deprecation notices to registered listeners.
73 | config.active_support.deprecation = :notify
74 |
75 | # Disable automatic flushing of the log to improve performance.
76 | # config.autoflush_log = false
77 |
78 | # Use default logging formatter so that PID and timestamp are not suppressed.
79 | config.log_formatter = ::Logger::Formatter.new
80 | end
81 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | App::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_assets = true
17 | config.static_cache_control = "public, max-age=3600"
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 | end
37 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure your secret_key_base is kept private
11 | # if you're sharing your code publicly.
12 | App::Application.config.secret_key_base = '27b186570a6fd4b0fbbb16c50a61dc99cb695db28beadceed09ee5717dbb1cd1b75042e548cc595fd1a2465a5313006f54f27b169856fb4db1db0ff50c9ae756'
13 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | App::Application.config.session_store :cookie_store, key: '_app_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/sorcery.rb:
--------------------------------------------------------------------------------
1 | # The first thing you need to configure is which modules you need in your app.
2 | # The default is nothing which will include only core features (password encryption, login/logout).
3 | # Available submodules are: :user_activation, :http_basic_auth, :remember_me,
4 | # :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external
5 | Rails.application.config.sorcery.submodules = [:reset_password]
6 |
7 | # Here you can configure each submodule's features.
8 | Rails.application.config.sorcery.configure do |config|
9 | # -- core --
10 | # What controller action to call for non-authenticated users. You can also
11 | # override the 'not_authenticated' method of course.
12 | # Default: `:not_authenticated`
13 | #
14 | # config.not_authenticated_action =
15 |
16 |
17 | # When a non logged in user tries to enter a page that requires login, save
18 | # the URL he wanted to reach, and send him there after login, using 'redirect_back_or_to'.
19 | # Default: `true`
20 | #
21 | # config.save_return_to_url =
22 |
23 |
24 | # Set domain option for cookies; Useful for remember_me submodule.
25 | # Default: `nil`
26 | #
27 | # config.cookie_domain =
28 |
29 |
30 | # -- session timeout --
31 | # How long in seconds to keep the session alive.
32 | # Default: `3600`
33 | #
34 | # config.session_timeout =
35 |
36 |
37 | # Use the last action as the beginning of session timeout.
38 | # Default: `false`
39 | #
40 | # config.session_timeout_from_last_action =
41 |
42 |
43 | # -- http_basic_auth --
44 | # What realm to display for which controller name. For example {"My App" => "Application"}
45 | # Default: `{"application" => "Application"}`
46 | #
47 | # config.controller_to_realm_map =
48 |
49 |
50 | # -- activity logging --
51 | # will register the time of last user login, every login.
52 | # Default: `true`
53 | #
54 | # config.register_login_time =
55 |
56 |
57 | # will register the time of last user logout, every logout.
58 | # Default: `true`
59 | #
60 | # config.register_logout_time =
61 |
62 |
63 | # will register the time of last user action, every action.
64 | # Default: `true`
65 | #
66 | # config.register_last_activity_time =
67 |
68 |
69 | # -- external --
70 | # What providers are supported by this app, i.e. [:twitter, :facebook, :github, :linkedin, :xing, :google, :liveid] .
71 | # Default: `[]`
72 | #
73 | # config.external_providers =
74 |
75 |
76 | # You can change it by your local ca_file. i.e. '/etc/pki/tls/certs/ca-bundle.crt'
77 | # Path to ca_file. By default use a internal ca-bundle.crt.
78 | # Default: `'path/to/ca_file'`
79 | #
80 | # config.ca_file =
81 |
82 |
83 | # For information about LinkedIn API:
84 | # - user info fields go to https://developer.linkedin.com/documents/profile-fields
85 | # - access permissions go to https://developer.linkedin.com/documents/authentication#granting
86 | #
87 | # config.linkedin.key = ""
88 | # config.linkedin.secret = ""
89 | # config.linkedin.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=linkedin"
90 | # config.linkedin.user_info_fields = ['first-name', 'last-name']
91 | # config.linkedin.user_info_mapping = {first_name: "firstName", last_name: "lastName"}
92 | # config.linkedin.access_permissions = ['r_basicprofile']
93 | #
94 | #
95 | # For information about XING API:
96 | # - user info fields go to https://dev.xing.com/docs/get/users/me
97 | #
98 | # config.xing.key = ""
99 | # config.xing.secret = ""
100 | # config.xing.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=xing"
101 | # config.xing.user_info_mapping = {first_name: "first_name", last_name: "last_name"}
102 | #
103 | #
104 | # Twitter wil not accept any requests nor redirect uri containing localhost,
105 | # make sure you use 0.0.0.0:3000 to access your app in development
106 | #
107 | # config.twitter.key = ""
108 | # config.twitter.secret = ""
109 | # config.twitter.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=twitter"
110 | # config.twitter.user_info_mapping = {:email => "screen_name"}
111 | #
112 | # config.facebook.key = ""
113 | # config.facebook.secret = ""
114 | # config.facebook.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=facebook"
115 | # config.facebook.user_info_mapping = {:email => "name"}
116 | # config.facebook.access_permissions = ["email", "publish_stream"]
117 | #
118 | # config.github.key = ""
119 | # config.github.secret = ""
120 | # config.github.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=github"
121 | # config.github.user_info_mapping = {:email => "name"}
122 | #
123 | # config.google.key = ""
124 | # config.google.secret = ""
125 | # config.google.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=google"
126 | # config.google.user_info_mapping = {:email => "email", :username => "name"}
127 | #
128 | # config.vk.key = ""
129 | # config.vk.secret = ""
130 | # config.vk.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=vk"
131 | # config.vk.user_info_mapping = {:login => "domain", :name => "full_name"}
132 | #
133 | # To use liveid in development mode you have to replace mydomain.com with
134 | # a valid domain even in development. To use a valid domain in development
135 | # simply add your domain in your /etc/hosts file in front of 127.0.0.1
136 | #
137 | # config.liveid.key = ""
138 | # config.liveid.secret = ""
139 | # config.liveid.callback_url = "http://mydomain.com:3000/oauth/callback?provider=liveid"
140 | # config.liveid.user_info_mapping = {:username => "name"}
141 |
142 |
143 | # --- user config ---
144 | config.user_config do |user|
145 | # -- core --
146 | # specify username attributes, for example: [:username, :email].
147 | # Default: `[:username]`
148 | #
149 | # user.username_attribute_names =
150 |
151 |
152 | # change *virtual* password attribute, the one which is used until an encrypted one is generated.
153 | # Default: `:password`
154 | #
155 | # user.password_attribute_name =
156 |
157 |
158 | # downcase the username before trying to authenticate, default is false
159 | # Default: `false`
160 | #
161 | # user.downcase_username_before_authenticating =
162 |
163 |
164 | # change default email attribute.
165 | # Default: `:email`
166 | #
167 | # user.email_attribute_name =
168 |
169 |
170 | # change default crypted_password attribute.
171 | # Default: `:crypted_password`
172 | #
173 | # user.crypted_password_attribute_name =
174 |
175 |
176 | # what pattern to use to join the password with the salt
177 | # Default: `""`
178 | #
179 | # user.salt_join_token =
180 |
181 |
182 | # change default salt attribute.
183 | # Default: `:salt`
184 | #
185 | # user.salt_attribute_name =
186 |
187 |
188 | # how many times to apply encryption to the password.
189 | # Default: `nil`
190 | #
191 | # user.stretches =
192 |
193 |
194 | # encryption key used to encrypt reversible encryptions such as AES256.
195 | # WARNING: If used for users' passwords, changing this key will leave passwords undecryptable!
196 | # Default: `nil`
197 | #
198 | # user.encryption_key =
199 |
200 |
201 | # use an external encryption class.
202 | # Default: `nil`
203 | #
204 | # user.custom_encryption_provider =
205 |
206 |
207 | # encryption algorithm name. See 'encryption_algorithm=' for available options.
208 | # Default: `:bcrypt`
209 | #
210 | # user.encryption_algorithm =
211 |
212 |
213 | # make this configuration inheritable for subclasses. Useful for ActiveRecord's STI.
214 | # Default: `false`
215 | #
216 | # user.subclasses_inherit_config =
217 |
218 |
219 | # -- remember_me --
220 | # allow the remember_me cookie to settable through AJAX
221 | # Default: `true`
222 | #
223 | # user.remember_me_httponly =
224 |
225 | # How long in seconds the session length will be
226 | # Default: `604800`
227 | #
228 | # user.remember_me_for =
229 |
230 |
231 | # -- user_activation --
232 | # the attribute name to hold activation state (active/pending).
233 | # Default: `:activation_state`
234 | #
235 | # user.activation_state_attribute_name =
236 |
237 |
238 | # the attribute name to hold activation code (sent by email).
239 | # Default: `:activation_token`
240 | #
241 | # user.activation_token_attribute_name =
242 |
243 |
244 | # the attribute name to hold activation code expiration date.
245 | # Default: `:activation_token_expires_at`
246 | #
247 | # user.activation_token_expires_at_attribute_name =
248 |
249 |
250 | # how many seconds before the activation code expires. nil for never expires.
251 | # Default: `nil`
252 | #
253 | # user.activation_token_expiration_period =
254 |
255 |
256 | # your mailer class. Required.
257 | # Default: `nil`
258 | #
259 | # user.user_activation_mailer =
260 |
261 |
262 | # when true sorcery will not automatically
263 | # email activation details and allow you to
264 | # manually handle how and when email is sent.
265 | # Default: `false`
266 | #
267 | # user.activation_mailer_disabled =
268 |
269 |
270 | # activation needed email method on your mailer class.
271 | # Default: `:activation_needed_email`
272 | #
273 | # user.activation_needed_email_method_name =
274 |
275 |
276 | # activation success email method on your mailer class.
277 | # Default: `:activation_success_email`
278 | #
279 | # user.activation_success_email_method_name =
280 |
281 |
282 | # do you want to prevent or allow users that did not activate by email to login?
283 | # Default: `true`
284 | #
285 | # user.prevent_non_active_users_to_login =
286 |
287 |
288 | # -- reset_password --
289 | # reset password code attribute name.
290 | # Default: `:reset_password_token`
291 | #
292 | # user.reset_password_token_attribute_name =
293 |
294 |
295 | # expires at attribute name.
296 | # Default: `:reset_password_token_expires_at`
297 | #
298 | # user.reset_password_token_expires_at_attribute_name =
299 |
300 |
301 | # when was email sent, used for hammering protection.
302 | # Default: `:reset_password_email_sent_at`
303 | #
304 | # user.reset_password_email_sent_at_attribute_name =
305 |
306 |
307 | # mailer class. Needed.
308 | # Default: `nil`
309 | #
310 | # user.reset_password_mailer = ResetPasswordMailer
311 |
312 |
313 | # reset password email method on your mailer class.
314 | # Default: `:reset_password_email`
315 | #
316 | # user.reset_password_email_method_name =
317 |
318 |
319 | # when true sorcery will not automatically
320 | # email password reset details and allow you to
321 | # manually handle how and when email is sent
322 | # Default: `false`
323 | #
324 | user.reset_password_mailer_disabled = true
325 |
326 |
327 | # how many seconds before the reset request expires. nil for never expires.
328 | # Default: `nil`
329 | #
330 | # user.reset_password_expiration_period =
331 |
332 |
333 | # hammering protection, how long to wait before allowing another email to be sent.
334 | # Default: `5 * 60`
335 | #
336 | # user.reset_password_time_between_emails =
337 |
338 |
339 | # -- brute_force_protection --
340 | # Failed logins attribute name.
341 | # Default: `:failed_logins_count`
342 | #
343 | # user.failed_logins_count_attribute_name =
344 |
345 |
346 | # This field indicates whether user is banned and when it will be active again.
347 | # Default: `:lock_expires_at`
348 | #
349 | # user.lock_expires_at_attribute_name =
350 |
351 |
352 | # How many failed logins allowed.
353 | # Default: `50`
354 | #
355 | # user.consecutive_login_retries_amount_limit =
356 |
357 |
358 | # How long the user should be banned. in seconds. 0 for permanent.
359 | # Default: `60 * 60`
360 | #
361 | # user.login_lock_time_period =
362 |
363 | # Unlock token attribute name
364 | # Default: `:unlock_token`
365 | #
366 | # user.unlock_token_attribute_name =
367 |
368 | # Unlock token mailer method
369 | # Default: `:send_unlock_token_email`
370 | #
371 | # user.unlock_token_email_method_name =
372 |
373 | # when true sorcery will not automatically
374 | # send email with unlock token
375 | # Default: `false`
376 | #
377 | # user.unlock_token_mailer_disabled = true
378 |
379 | # Unlock token mailer class
380 | # Default: `nil`
381 | #
382 | # user.unlock_token_mailer = UserMailer
383 |
384 | # -- activity logging --
385 | # Last login attribute name.
386 | # Default: `:last_login_at`
387 | #
388 | # user.last_login_at_attribute_name =
389 |
390 |
391 | # Last logout attribute name.
392 | # Default: `:last_logout_at`
393 | #
394 | # user.last_logout_at_attribute_name =
395 |
396 |
397 | # Last activity attribute name.
398 | # Default: `:last_activity_at`
399 | #
400 | # user.last_activity_at_attribute_name =
401 |
402 |
403 | # How long since last activity is he user defined logged out?
404 | # Default: `10 * 60`
405 | #
406 | # user.activity_timeout =
407 |
408 |
409 | # -- external --
410 | # Class which holds the various external provider data for this user.
411 | # Default: `nil`
412 | #
413 | # user.authentications_class =
414 |
415 |
416 | # User's identifier in authentications class.
417 | # Default: `:user_id`
418 | #
419 | # user.authentications_user_id_attribute_name =
420 |
421 |
422 | # Provider's identifier in authentications class.
423 | # Default: `:provider`
424 | #
425 | # user.provider_attribute_name =
426 |
427 |
428 | # User's external unique identifier in authentications class.
429 | # Default: `:uid`
430 | #
431 | # user.provider_uid_attribute_name =
432 | end
433 |
434 | # This line must come after the 'user config' block.
435 | # Define which model authenticates with sorcery.
436 | config.user_class = "User"
437 | end
438 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/the_comments.rb:
--------------------------------------------------------------------------------
1 | # TheComments.config.param_name => value
2 |
3 | TheComments.configure do |config|
4 | config.max_reply_depth = 3 # comments tree depth
5 | config.tolerance_time = 5 # sec - after this delay user can post a comment
6 | config.default_state = :draft # default state for comment
7 | config.default_owner_state = :published # default state for comment for Moderator
8 | config.empty_inputs = [:commentBody] # array of spam trap fields
9 | config.default_title = 'Undefined title' # default commentable_title for denormalization
10 | config.template_engine = :haml
11 |
12 | config.empty_trap_protection = true
13 | config.tolerance_time_protection = true
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy_app/config/routes.rb:
--------------------------------------------------------------------------------
1 | App::Application.routes.draw do
2 | root 'posts#index'
3 |
4 | get "autologin/:id" => "users#autologin", as: :autologin
5 |
6 | # Login system
7 | get "login" => "sessions#new", as: :login
8 | delete "logout" => "sessions#destroy", as: :logout
9 | get "signup" => "users#new", as: :signup
10 | post "sessions" => "sessions#create", as: :sessions
11 |
12 | resources :posts
13 | resources :users
14 |
15 | # TheComments routes
16 | concern :user_comments, TheComments::UserRoutes.new
17 | concern :admin_comments, TheComments::AdminRoutes.new
18 | resources :comments, concerns: [:user_comments, :admin_comments]
19 | end
20 |
--------------------------------------------------------------------------------
/spec/dummy_app/db/migrate/20130712061503_sorcery_core.rb:
--------------------------------------------------------------------------------
1 | class SorceryCore < ActiveRecord::Migration
2 | def self.up
3 | create_table :users do |t|
4 | t.string :username, :null => false # if you use another field as a username, for example email, you can safely remove this field.
5 | t.string :email, :default => nil # if you use this field as a username, you might want to make it :null => false.
6 | t.string :crypted_password, :default => nil
7 | t.string :salt, :default => nil
8 |
9 | t.timestamps
10 | end
11 | end
12 |
13 | def self.down
14 | drop_table :users
15 | end
16 | end
--------------------------------------------------------------------------------
/spec/dummy_app/db/migrate/20130712065951_create_posts.rb:
--------------------------------------------------------------------------------
1 | class CreatePosts < ActiveRecord::Migration
2 | def change
3 | create_table :posts do |t|
4 | t.integer :user_id
5 | t.string :title
6 | t.text :content
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy_app/db/migrate/20131027185332_change_user.the_comments_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from the_comments_engine (originally 20130101010101)
2 | # null: false => de-facto db-level validation
3 | class ChangeUser < ActiveRecord::Migration
4 | def change
5 | change_table :users do |t|
6 | # "Written by me" (cache counters)
7 | t.integer :my_draft_comments_count, default: 0
8 | t.integer :my_published_comments_count, default: 0
9 | t.integer :my_comments_count, default: 0 # my_draft_comments_count + my_published_comments_count
10 |
11 | # commentable's comments => comcoms (cache counters)
12 | # Relation through Comment#holder_id field
13 | t.integer :draft_comcoms_count, default: 0
14 | t.integer :published_comcoms_count, default: 0
15 | t.integer :deleted_comcoms_count, default: 0
16 | t.integer :spam_comcoms_count, default: 0
17 | end
18 | end
19 | end
--------------------------------------------------------------------------------
/spec/dummy_app/db/migrate/20131027185333_create_comments.the_comments_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from the_comments_engine (originally 20130101010102)
2 | class CreateComments < ActiveRecord::Migration
3 | def change
4 | create_table :comments do |t|
5 | # relations
6 | t.integer :user_id
7 | t.integer :holder_id
8 |
9 | # polymorphic, commentable object
10 | t.integer :commentable_id
11 | t.string :commentable_type
12 |
13 | # denormalization
14 | t.string :commentable_url
15 | t.string :commentable_title
16 | t.string :commentable_state
17 |
18 | # comment
19 | t.string :anchor
20 |
21 | t.string :title
22 | t.string :contacts
23 |
24 | t.text :raw_content
25 | t.text :content
26 |
27 | # moderation token
28 | t.string :view_token
29 |
30 | # state machine => :draft | :published | :deleted
31 | t.string :state, default: :draft
32 |
33 | # base user data (BanHammer power)
34 | t.string :ip, default: :undefined
35 | t.string :referer, default: :undefined
36 | t.string :user_agent, default: :undefined
37 | t.integer :tolerance_time
38 |
39 | # unusable: for future versions
40 | t.boolean :spam, default: false
41 |
42 | # nested set
43 | t.integer :parent_id
44 | t.integer :lft
45 | t.integer :rgt
46 | t.integer :depth, default: 0
47 |
48 | t.timestamps
49 | end
50 | end
51 | end
--------------------------------------------------------------------------------
/spec/dummy_app/db/migrate/20131027185334_change_commentable.the_comments_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from the_comments_engine (originally 20130101010103)
2 | class ChangeCommentable < ActiveRecord::Migration
3 | def change
4 | # Uncomment this. Add fields to Commentable Models
5 | #
6 | [:users, :posts].each do |table_name|
7 | change_table table_name do |t|
8 | t.integer :draft_comments_count, default: 0
9 | t.integer :published_comments_count, default: 0
10 | t.integer :deleted_comments_count, default: 0
11 | end
12 | end
13 | end
14 | end
--------------------------------------------------------------------------------
/spec/dummy_app/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20131027185334) do
15 |
16 | create_table "comments", force: true do |t|
17 | t.integer "user_id"
18 | t.integer "holder_id"
19 | t.integer "commentable_id"
20 | t.string "commentable_type"
21 | t.string "commentable_url"
22 | t.string "commentable_title"
23 | t.string "commentable_state"
24 | t.string "anchor"
25 | t.string "title"
26 | t.string "contacts"
27 | t.text "raw_content"
28 | t.text "content"
29 | t.string "view_token"
30 | t.string "state", default: "draft"
31 | t.string "ip", default: "undefined"
32 | t.string "referer", default: "undefined"
33 | t.string "user_agent", default: "undefined"
34 | t.integer "tolerance_time"
35 | t.boolean "spam", default: false
36 | t.integer "parent_id"
37 | t.integer "lft"
38 | t.integer "rgt"
39 | t.integer "depth", default: 0
40 | t.datetime "created_at"
41 | t.datetime "updated_at"
42 | end
43 |
44 | create_table "posts", force: true do |t|
45 | t.integer "user_id"
46 | t.string "title"
47 | t.text "content"
48 | t.datetime "created_at"
49 | t.datetime "updated_at"
50 | t.integer "draft_comments_count", default: 0
51 | t.integer "published_comments_count", default: 0
52 | t.integer "deleted_comments_count", default: 0
53 | end
54 |
55 | create_table "users", force: true do |t|
56 | t.string "username", null: false
57 | t.string "email"
58 | t.string "crypted_password"
59 | t.string "salt"
60 | t.datetime "created_at"
61 | t.datetime "updated_at"
62 | t.integer "my_draft_comments_count", default: 0
63 | t.integer "my_published_comments_count", default: 0
64 | t.integer "my_comments_count", default: 0
65 | t.integer "draft_comcoms_count", default: 0
66 | t.integer "published_comcoms_count", default: 0
67 | t.integer "deleted_comcoms_count", default: 0
68 | t.integer "spam_comcoms_count", default: 0
69 | t.integer "draft_comments_count", default: 0
70 | t.integer "published_comments_count", default: 0
71 | t.integer "deleted_comments_count", default: 0
72 | end
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/spec/dummy_app/db/seeds.rb:
--------------------------------------------------------------------------------
1 | puts 'Users'
2 |
3 | 3.times do
4 | User.create!(username: Faker::Name.name)
5 | print '.'
6 | end
7 |
8 | puts
9 | puts 'Posts'
10 |
11 | User.all.each do |user|
12 | 3.times do
13 | user.posts.create!(
14 | title: Faker::Lorem.sentence,
15 | content: Faker::Lorem.paragraphs(3).join
16 | )
17 | print '.'
18 | end
19 | end
20 |
21 | puts
22 | puts 'Comments'
23 |
24 | users = User.all
25 | posts = Post.all
26 |
27 | 20.times do
28 | user = users.sample
29 | post = posts.sample
30 |
31 | Comment.create!(
32 | user: user,
33 | commentable: post,
34 | title: Faker::Lorem.sentence,
35 | raw_content: Faker::Lorem.paragraphs(3).join,
36 | state: :published
37 | )
38 |
39 | print '.'
40 | end
41 |
42 | puts
--------------------------------------------------------------------------------
/spec/dummy_app/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/lib/tasks/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/lib/tasks/app_bootstrap.rake:
--------------------------------------------------------------------------------
1 | namespace :db do
2 | desc "DB: drop, create, migrate"
3 | task bootstrap: :environment do
4 | Rake::Task["db:drop"].invoke
5 | Rake::Task["db:create"].invoke
6 | Rake::Task["db:migrate"].invoke
7 | end
8 |
9 | # rake db:bootstrap_and_seed
10 | desc "Reset DB and seed"
11 | task bootstrap_and_seed: :environment do
12 | Rake::Task["db:bootstrap"].invoke
13 | Rake::Task["db:seed"].invoke
14 | end
15 | end
--------------------------------------------------------------------------------
/spec/dummy_app/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/log/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The page you were looking for doesn't exist.
54 |
You may have mistyped the address or the page may have moved.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/dummy_app/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The change you wanted was rejected.
54 |
Maybe you tried to change something you didn't have access to.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/dummy_app/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
We're sorry, but something went wrong.
54 |
55 | If you are the application owner check the logs for more information.
56 |
57 |
58 |
--------------------------------------------------------------------------------
/spec/dummy_app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/public/favicon.ico
--------------------------------------------------------------------------------
/spec/dummy_app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/spec/dummy_app/spec/factories/post.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :post, class: Post do
3 | sequence(:title) { Faker::Lorem.sentence }
4 | sequence(:content) { Faker::Lorem.sentence }
5 | end
6 | end
--------------------------------------------------------------------------------
/spec/dummy_app/spec/factories/user.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | factory :user, class: User do
3 | sequence(:username) { Faker::Name.name }
4 | sequence(:email) { Faker::Internet.email }
5 | end
6 | end
--------------------------------------------------------------------------------
/spec/dummy_app/spec/models/user_counters_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | def destroy_all
4 | User.destroy_all
5 | 3.times{ begin; Comment.destroy_all; rescue; end; }
6 | end
7 |
8 | # --------------------------------------
9 | # Helpers
10 | # --------------------------------------
11 | def my_comments_count_assert user, count
12 | user.my_comments.active.count.should eq count
13 | user.my_comments_count.should eq count
14 | end
15 |
16 | def comments_count_assert(obj, values)
17 | obj.comments.with_state(:draft).count.should eq values[0]
18 | obj.comments.with_state(:published).count.should eq values[1]
19 | obj.comments.with_state(:deleted).count.should eq values[2]
20 | end
21 |
22 | def comments_counters_assert(obj, values)
23 | obj.draft_comments_count.should eq values[0]
24 | obj.published_comments_count.should eq values[1]
25 | obj.deleted_comments_count.should eq values[2]
26 | end
27 |
28 | def comcoms_count_assert(obj, values)
29 | obj.comcoms.with_state(:draft).count.should eq values[0]
30 | obj.comcoms.with_state(:published).count.should eq values[1]
31 | obj.comcoms.with_state(:deleted).count.should eq values[2]
32 | end
33 |
34 | def comcoms_counters_assert(obj, values)
35 | obj.draft_comcoms_count.should eq values[0]
36 | obj.published_comcoms_count.should eq values[1]
37 | obj.deleted_comcoms_count.should eq values[2]
38 | end
39 |
40 | # --------------------------------------
41 | # init functions
42 | # --------------------------------------
43 | def create_users_and_post
44 | @user = FactoryGirl.create(:user)
45 | @post_holder = FactoryGirl.create(:user)
46 | @post = FactoryGirl.create(:post, user: @post_holder)
47 | end
48 |
49 | def base_test_situation
50 | create_users_and_post
51 |
52 | 3.times do
53 | @comment = Comment.create!(
54 | user: @user,
55 | commentable: @post,
56 | title: Faker::Lorem.sentence,
57 | raw_content: Faker::Lorem.paragraphs(3).join
58 | )
59 | end
60 |
61 | @user.reload
62 | @post_holder.reload
63 | end
64 |
65 | def tree_test_situation
66 | create_users_and_post
67 |
68 | # LEVEL 1
69 | 3.times do
70 | Comment.create!(
71 | user: @user,
72 | commentable: @post,
73 | title: Faker::Lorem.sentence,
74 | raw_content: Faker::Lorem.paragraphs(3).join,
75 | state: :published
76 | )
77 | end
78 | # LEVEL 2
79 | parent_comment = Comment.first
80 | 3.times do
81 | Comment.create!(
82 | user: @user,
83 | commentable: @post,
84 | parent_id: parent_comment.id,
85 | title: Faker::Lorem.sentence,
86 | raw_content: Faker::Lorem.paragraphs(3).join,
87 | state: :published
88 | )
89 | end
90 | # LEVEL 3
91 | parent_comment = Comment.first.children.first
92 | 3.times do
93 | Comment.create!(
94 | user: @user,
95 | commentable: @post,
96 | parent_id: parent_comment.id,
97 | title: Faker::Lorem.sentence,
98 | raw_content: Faker::Lorem.paragraphs(3).join,
99 | state: :published
100 | )
101 | end
102 |
103 | @user.reload
104 | @post_holder.reload
105 | end
106 | # --------------------------------------
107 | # ~ init functions
108 | # --------------------------------------
109 |
110 | describe User do
111 | context 'User leave comment to the post' do
112 | after(:all){ destroy_all }
113 |
114 | before(:all){ create_users_and_post }
115 |
116 | it 'should be User' do
117 | @user.should be_instance_of User
118 | @post_holder.should be_instance_of User
119 | end
120 |
121 | it 'Post should has owner' do
122 | @post.user.should eq @post_holder
123 | end
124 |
125 | it 'should be Post' do
126 | @post.should be_instance_of Post
127 | end
128 |
129 | it 'Create new comment' do
130 | @comment = Comment.create!(
131 | user: @user,
132 | commentable: @post,
133 | title: Faker::Lorem.sentence,
134 | raw_content: Faker::Lorem.paragraphs(3).join
135 | )
136 |
137 | @comment.user.should eq @user
138 | @comment.holder.should eq @post_holder
139 | end
140 | end
141 |
142 | context "Written by me counters" do
143 | after(:all) { destroy_all }
144 | before(:all){ create_users_and_post }
145 | it 'should has correct My counters values' do
146 | @comment = Comment.create!(
147 | user: @user,
148 | commentable: @post,
149 | title: Faker::Lorem.sentence,
150 | raw_content: Faker::Lorem.paragraphs(3).join
151 | )
152 | @user.reload
153 | @user.my_comments_count.should eq 1
154 | @user.my_draft_comments_count.should eq 1
155 | @user.my_published_comments_count.should eq 0
156 | end
157 | end
158 |
159 | context "Commentable Denormalization" do
160 | after(:all) { destroy_all }
161 | before(:all) do
162 | base_test_situation
163 | end
164 |
165 | it 'should have denormalized fields' do
166 | title = "New Title!"
167 | @post.update_attribute(:title, title)
168 | @post.title.should eq title
169 | @comment = @post.comments.first
170 | @comment.commentable_title.should eq title
171 | end
172 | end
173 |
174 | context 'User leave 3 comments and Instances has expectable counter values' do
175 | after(:all) { destroy_all }
176 |
177 | before(:all){ base_test_situation }
178 |
179 | describe 'User counters' do
180 | it 'my_comments counter' do
181 | my_comments_count_assert(@user, 3)
182 | end
183 |
184 | it 'Comcoms counters' do
185 | @user.comcoms.count.should eq 0
186 |
187 | comcoms_counters_assert(@user, [0,0,0])
188 | end
189 | end
190 |
191 | describe 'Holder counters' do
192 | it 'my_comments counter' do
193 | my_comments_count_assert(@post_holder, 0)
194 | end
195 |
196 | it 'Comcoms counters' do
197 | @post_holder.comcoms.count.should eq 3
198 | comcoms_counters_assert(@post_holder, [3,0,0])
199 | end
200 | end
201 | end
202 |
203 | context 'User leave 3 comments, 1 Comment DRAFT => PUBLISHED' do
204 | after(:all) { destroy_all }
205 |
206 | before(:all) do
207 | base_test_situation
208 | @comment = Comment.first
209 | @comment.to_published
210 |
211 | @user.reload
212 | @post.reload
213 | @comment.reload
214 | @post_holder.reload
215 | end
216 |
217 | describe 'User counters' do
218 | it 'has expectable values' do
219 | comments_counters_assert @user, [0,0,0]
220 | comments_count_assert @user, [0,0,0]
221 | comcoms_counters_assert @user, [0,0,0]
222 | comcoms_count_assert @user, [0,0,0]
223 | my_comments_count_assert @user, 3
224 | end
225 | end
226 |
227 | describe 'Holder counters' do
228 | it 'has expectable values' do
229 | @post_holder.comcoms.count.should eq 3
230 |
231 | my_comments_count_assert(@post_holder, 0)
232 | comcoms_count_assert @post_holder, [2,1,0]
233 | comcoms_counters_assert @post_holder, [2,1,0]
234 | end
235 | end
236 | end
237 |
238 | context '3 comments, 1 Comment DRAFT => DELETE' do
239 | after(:all) { destroy_all }
240 |
241 | before(:all) do
242 | base_test_situation
243 | @comment = Comment.first
244 | @comment.to_published
245 | @comment.to_deleted
246 |
247 | @user.reload
248 | @post.reload
249 | @comment.reload
250 | @post_holder.reload
251 | end
252 |
253 | it 'has correct counters values' do
254 | comments_counters_assert @user, [0,0,0]
255 | comments_count_assert @user, [0,0,0]
256 | comcoms_count_assert @user, [0,0,0]
257 | my_comments_count_assert @user, 2
258 | @user.my_comments.count.should eq 3
259 |
260 | comments_counters_assert @post_holder, [0,0,0]
261 | comments_count_assert @post_holder, [0,0,0]
262 | comcoms_count_assert @post_holder, [2,0,1]
263 | my_comments_count_assert @post_holder, 0
264 | @post_holder.my_comments.count.should eq 0
265 |
266 | comments_count_assert @post, [2,0,1]
267 | comments_counters_assert @post, [2,0,1]
268 | end
269 | end
270 |
271 | context 'Comments tree, 1 branch to deleted' do
272 | after(:each) { destroy_all }
273 | before(:each){ tree_test_situation }
274 |
275 | it 'has correct counters values before deleting' do
276 | Comment.count.should eq 9
277 |
278 | my_comments_count_assert @user, 9
279 | comments_count_assert @user, [0,0,0]
280 |
281 | my_comments_count_assert @post_holder, 0
282 | comments_count_assert @post_holder, [0,0,0]
283 |
284 | comcoms_count_assert @post_holder, [0,9,0]
285 | comcoms_counters_assert @post_holder, [0,9,0]
286 |
287 | comments_count_assert @post, [0,9,0]
288 | comments_counters_assert @post, [0,9,0]
289 | end
290 |
291 | it 'has correct counters values after branch deleting' do
292 | @comment = Comment.first.children.first
293 | @comment.to_deleted
294 |
295 | @user.reload
296 | @post.reload
297 | @post_holder.reload
298 |
299 | Comment.with_state(:published).count.should eq 5
300 | Comment.with_state(:deleted).count.should eq 4
301 |
302 | my_comments_count_assert @user, 5
303 | comments_count_assert @user, [0,0,0]
304 |
305 | my_comments_count_assert @post_holder, 0
306 | comments_count_assert @post_holder, [0,0,0]
307 |
308 | comcoms_count_assert @post_holder, [0,5,4]
309 | comcoms_counters_assert @post_holder, [0,5,4]
310 |
311 | comments_count_assert @post, [0,5,4]
312 | comments_counters_assert @post, [0,5,4]
313 | end
314 |
315 | # This code helps to me to catch logic bugs
316 | # there is no any asserts
317 | # context "callbacks catch" do
318 | # after(:all) { destroy_all }
319 | # it "before validation callback with increment! must be call manually" do
320 | # @user = FactoryGirl.create(:user)
321 | # @post_holder = FactoryGirl.create(:user)
322 | # @post = FactoryGirl.create(:post, user: @post_holder)
323 |
324 | # Comment.create!(
325 | # user: @user,
326 | # commentable: @post,
327 | # title: Faker::Lorem.sentence,
328 | # raw_content: Faker::Lorem.paragraphs(3).join,
329 | # state: :deleted
330 | # )
331 | # Comment.first.to_published
332 | # end
333 | # end
334 |
335 | # it 'has correct counters after comment destroy' do
336 | # pending("has correct counters after comment destroy")
337 | # end
338 | end
339 | end
340 |
--------------------------------------------------------------------------------
/spec/dummy_app/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= 'test'
2 | require File.expand_path("../../config/environment", __FILE__)
3 |
4 | require 'rspec/rails'
5 | require 'rspec/autorun'
6 |
7 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
8 | ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
9 |
10 | RSpec.configure do |config|
11 | config.infer_base_class_for_anonymous_controllers = false
12 | config.fixture_path = "#{::Rails.root}/spec/fixtures"
13 | config.use_transactional_fixtures = true
14 | config.order = "random"
15 |
16 | # DatabaseCleaner
17 | # config.before(:suite) do
18 | # DatabaseCleaner.strategy = :transaction
19 | # DatabaseCleaner.clean_with(:truncation)
20 | # end
21 |
22 | # config.before(:each) do
23 | # DatabaseCleaner.start
24 | # end
25 |
26 | # config.after(:each) do
27 | # DatabaseCleaner.clean
28 | # end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/dummy_app/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/test/controllers/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/test/fixtures/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/test/fixtures/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/test/helpers/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/test/integration/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/test/mailers/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/test/models/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "test"
2 | require File.expand_path('../../config/environment', __FILE__)
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | ActiveRecord::Migration.check_pending!
7 |
8 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
9 | #
10 | # Note: You'll currently still have to declare fixtures explicitly in integration tests
11 | # -- they do not yet inherit this setting
12 | fixtures :all
13 |
14 | # Add more helper methods to be used by all tests here...
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy_app/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/vendor/assets/javascripts/.keep
--------------------------------------------------------------------------------
/spec/dummy_app/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-trash/the_comments/5992b861f88100ed49058afad623b0d744fcc055/spec/dummy_app/vendor/assets/stylesheets/.keep
--------------------------------------------------------------------------------
/the_comments.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'the_comments/version'
5 |
6 | Gem::Specification.new do |gem|
7 | gem.name = "the_comments"
8 | gem.version = TheComments::VERSION
9 | gem.authors = ["Ilya N. Zykin"]
10 | gem.email = ["zykin-ilya@ya.ru"]
11 | gem.description = %q{ Comments with threading for Rails 4 }
12 | gem.summary = %q{ the_comments by the-teacher }
13 | gem.homepage = "https://github.com/the-teacher/the_comments"
14 |
15 | gem.files = `git ls-files`.split($/)
16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18 | gem.require_paths = ["lib"]
19 |
20 | gem.add_dependency 'state_machine', '~> 1.2.0'
21 | gem.add_dependency 'the_sortable_tree', '~> 2.5.0'
22 | gem.add_dependency 'the_simple_sort', '~> 0.0.2'
23 |
24 | # gem.add_dependency 'rails', '>= 4.0'
25 | end
26 |
--------------------------------------------------------------------------------
/the_comments.yml.teamocil.example:
--------------------------------------------------------------------------------
1 | windows:
2 | - name: "TheComments"
3 | root: "~/rails/opensource/the_comments"
4 | filters:
5 | before: "rvm use ruby-2.0.0-p353; rvm gemset use the_comments --create"
6 | layout: tiled
7 | panes:
8 | - cmd: "clear;"
9 | - cmd: "clear;"
10 | - cmd: "cd spec/dummy_app; clear"
11 | - cmd: "cd spec/dummy_app; clear; rake db:bootstrap RAILS_ENV=test; rspec"
--------------------------------------------------------------------------------
/views_converter.rb:
--------------------------------------------------------------------------------
1 | # gem install haml2slim --no-ri --no-rdoc
2 | # ruby views_converter.rb
3 |
4 | from = "./app/views/the_comments/haml"
5 | to = "./app/views/the_comments/slim"
6 |
7 | `haml2slim #{from} #{to} --trace`
8 |
9 | Dir.glob("#{to}/*.slim") do |slim_file|
10 | content = File.read slim_file
11 | content = content.gsub "haml", "slim"
12 |
13 | file = File.open slim_file, "w"
14 | file.write content
15 | file.close
16 | end
17 |
--------------------------------------------------------------------------------