thredded 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -19
  3. data/app/commands/thredded/create_messageboard.rb +2 -2
  4. data/app/forms/thredded/private_topic_form.rb +1 -1
  5. data/app/helpers/thredded/application_helper.rb +2 -2
  6. data/app/helpers/thredded/urls_helper.rb +2 -0
  7. data/app/mailer_previews/thredded/base_mailer_preview.rb +5 -5
  8. data/app/models/concerns/thredded/friendly_id_reserved_words_and_pagination.rb +1 -1
  9. data/app/models/concerns/thredded/notifier_preference.rb +1 -1
  10. data/app/models/concerns/thredded/post_common.rb +3 -3
  11. data/app/models/thredded/post_moderation_record.rb +4 -4
  12. data/app/view_models/thredded/topics_page_view.rb +1 -0
  13. data/app/views/thredded/moderation/_post_moderation_record.html.erb +3 -3
  14. data/app/views/thredded/shared/nav/_standalone.html.erb +2 -2
  15. data/bin/rubocop +29 -0
  16. data/bin/run_specs_repeatedly +9 -0
  17. data/config/i18n-tasks.yml +1 -1
  18. data/db/upgrade_migrations/20161019150201_upgrade_v0_7_to_v0_8.rb +5 -5
  19. data/lib/generators/thredded/install/templates/initializer.rb +1 -1
  20. data/lib/thredded/arel_compat.rb +1 -1
  21. data/lib/thredded/base_migration.rb +1 -1
  22. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +29 -39
  23. data/lib/thredded/compat.rb +0 -6
  24. data/lib/thredded/content_formatter.rb +23 -8
  25. data/lib/thredded/db_tools.rb +2 -6
  26. data/lib/thredded/engine.rb +1 -1
  27. data/lib/thredded/formatting_demo_content.rb +21 -21
  28. data/lib/thredded/html_pipeline/at_mention_filter.rb +1 -1
  29. data/lib/thredded/html_pipeline/onebox_filter.rb +3 -3
  30. data/lib/thredded/html_pipeline/utils.rb +1 -1
  31. data/lib/thredded/version.rb +1 -1
  32. metadata +24 -15
  33. data/lib/thredded/rails_lt_5_2_arel_case_node.rb +0 -119
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a7da465ff23d46ffb17f30aef22bc8195dc7551f485d28d698f3a8e20aaf958
4
- data.tar.gz: 24d752703abdc40ba28ffa48d931d70d5c9baeff461467059e7f2c2fbdfea27c
3
+ metadata.gz: 19cde941264a9f9b9427694c27cc5f2a668fd18debbfe459d3fc58087e5636bf
4
+ data.tar.gz: 0d4eec1e395f2265783f9283ad958ec2a7f486a017bb961af87b26c2fd711c20
5
5
  SHA512:
6
- metadata.gz: 6dec36328aba9edd1143d2aecaa70970ba48897ed470e97785c534067c6d5f38e0e175d4637a5824f1d09827059ab508147c2c721400a8027e966884f235a74d
7
- data.tar.gz: c35c5f19f198b78b0d6426b96e1f15863cb29ff74c5a130d665a1fde1dd89d9f5af8bb194b0991d2a7bf965aa9fe6309af0de03246a6bf7d2711e1356b34d636
6
+ metadata.gz: f7d6aa4fd6d1eaecda06e839b757d6fa2bbf7fd24ae6ef4ba3c238a5d8880baeff1be6b9ee2aae21b34c8f844d0aa5eb5c41a1f70e7841cbe867a900e29b980a
7
+ data.tar.gz: 04af99833299683e35b13468e957bd75880a1627db1ff6c9ab53e1678f99842b273cc50ed529c9cddeedb7600c0d54b404550fd8858a7901346d6c72df77bdb2
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Thredded [![Code Climate](https://codeclimate.com/github/thredded/thredded/badges/gpa.svg)](https://codeclimate.com/github/thredded/thredded) [![Travis-CI](https://api.travis-ci.org/thredded/thredded.svg?branch=master)](https://travis-ci.org/thredded/thredded/) [![Test Coverage](https://codeclimate.com/github/thredded/thredded/badges/coverage.svg)](https://codeclimate.com/github/thredded/thredded/coverage) [![Inline docs](http://inch-ci.org/github/thredded/thredded.svg?branch=master)](http://inch-ci.org/github/thredded/thredded) [![Gitter](https://badges.gitter.im/thredded/thredded.svg)](https://gitter.im/thredded/thredded?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
1
+ # Thredded [![Code Climate](https://codeclimate.com/github/thredded/thredded/badges/gpa.svg)](https://codeclimate.com/github/thredded/thredded) [![Travis-CI](https://api.travis-ci.org/thredded/thredded.svg?branch=main)](https://travis-ci.org/thredded/thredded/) [![Test Coverage](https://codeclimate.com/github/thredded/thredded/badges/coverage.svg)](https://codeclimate.com/github/thredded/thredded/coverage) [![Inline docs](http://inch-ci.org/github/thredded/thredded.svg?branch=main)](http://inch-ci.org/github/thredded/thredded) [![Gitter](https://badges.gitter.im/thredded/thredded.svg)](https://gitter.im/thredded/thredded?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
2
2
 
3
- _Thredded_ is a Rails 4.2+ forum/messageboard engine. Its goal is to be as simple and feature rich as possible.
3
+ _Thredded_ is a Rails 6.0+ forum/messageboard engine. Its goal is to be as simple and feature rich as possible.
4
4
 
5
5
  Some of the features currently in Thredded:
6
6
 
@@ -38,6 +38,7 @@ Table of Contents
38
38
  * [Adding Thredded to an existing Rails app](#adding-thredded-to-an-existing-rails-app)
39
39
  * [Upgrading an existing install](#upgrading-an-existing-install)
40
40
  * [Migrating from Forem](#migrating-from-forem)
41
+ * [Rails compatibility](#rails-compatibility)
41
42
  * [Views and other assets](#views-and-other-assets)
42
43
  * [Standalone layout](#standalone-layout)
43
44
  * [Application layout](#application-layout)
@@ -96,7 +97,7 @@ Then, see the rest of this Readme for more information about using and customizi
96
97
  Add the gem to your Gemfile:
97
98
 
98
99
  ```ruby
99
- gem 'thredded', '~> 1.0'
100
+ gem 'thredded', '~> 1.1'
100
101
  ```
101
102
 
102
103
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -155,6 +156,15 @@ to Thredded.
155
156
  [forem-to-thredded]: https://github.com/thredded/thredded/wiki/Migrate-from-Forem
156
157
  [Forem]: https://github.com/rubysherpas/forem
157
158
 
159
+ ## Rails compatibility
160
+
161
+ | Rails | Latest Thredded |
162
+ | ------------- |------------------|
163
+ | Rails 6.0+ | Thredded 1.1+ |
164
+ | Rails 5.2 | Thredded 1.0.1 |
165
+ | Rails 4.2 | Thredded 0.16.16 |
166
+
167
+
158
168
  ## Views and other assets
159
169
 
160
170
  ### Standalone layout
@@ -352,7 +362,7 @@ with a "themed" version of thredded.
352
362
  #### Styles
353
363
 
354
364
  Thredded comes with a light Sass theme controlled by a handful of variables that can be found here:
355
- https://github.com/thredded/thredded/blob/master/app/assets/stylesheets/thredded/base/_variables.scss.
365
+ https://github.com/thredded/thredded/blob/main/app/assets/stylesheets/thredded/base/_variables.scss.
356
366
 
357
367
  To override the styles, override the variables *before* importing Thredded styles, e.g.:
358
368
 
@@ -366,8 +376,8 @@ If you are writing a Thredded plugin, import the [`thredded/base`][thredded-scss
366
376
  The `base` package only defines variables, mixins, and %-placeholders, so it can be imported safely without producing
367
377
  any duplicate CSS.
368
378
 
369
- [thredded-scss-dependencies]: https://github.com/thredded/thredded/blob/master/app/assets/stylesheets/thredded/_dependencies.scss
370
- [thredded-scss-base]: https://github.com/thredded/thredded/blob/master/app/assets/stylesheets/thredded/_base.scss
379
+ [thredded-scss-dependencies]: https://github.com/thredded/thredded/blob/main/app/assets/stylesheets/thredded/_dependencies.scss
380
+ [thredded-scss-base]: https://github.com/thredded/thredded/blob/main/app/assets/stylesheets/thredded/_base.scss
371
381
 
372
382
  ### Email and other notifications
373
383
 
@@ -513,35 +523,35 @@ Below is an overview of the default permissions, with links to the implementatio
513
523
  <tbody>
514
524
  <tr>
515
525
  <th align="center">Logged in</th>
516
- <td align="center" rowspan="2"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/read/all.rb">
526
+ <td align="center" rowspan="2"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/read/all.rb">
517
527
  ✅ All
518
528
  </a></td>
519
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/write/all.rb">
529
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/write/all.rb">
520
530
  ✅ All
521
531
  </a></td>
522
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb">
532
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb">
523
533
  Readers of the messageboards<br>the user can post in
524
534
  </a></td>
525
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb">
535
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb">
526
536
  <code>moderator_column</code>
527
537
  </a></td>
528
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/admin/if_admin_column_true.rb">
538
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/admin/if_admin_column_true.rb">
529
539
  <code>admin_column</code>
530
540
  </a></td>
531
541
  </tr>
532
542
  <tr>
533
543
  <th align="center">Not logged in</th>
534
544
  <!-- rowspan -->
535
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/write/none.rb">
545
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/write/none.rb">
536
546
  ❌ No
537
547
  </a></td>
538
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb">
548
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb">
539
549
  ❌ No
540
550
  </a></td>
541
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/moderate/none.rb">
551
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/moderate/none.rb">
542
552
  ❌ No
543
553
  </a></td>
544
- <td align="center"><a href="https://github.com/thredded/thredded/blob/master/app/models/thredded/user_permissions/admin/none.rb">
554
+ <td align="center"><a href="https://github.com/thredded/thredded/blob/main/app/models/thredded/user_permissions/admin/none.rb">
545
555
  ❌ No
546
556
  </a></td>
547
557
  </tr>
@@ -551,7 +561,7 @@ Below is an overview of the default permissions, with links to the implementatio
551
561
  ### Handling "Permission denied" and "Not found" errors
552
562
 
553
563
  Thredded defines a number of Exception classes for not found / permission denied errors.
554
- The complete list can be found [here](https://github.com/thredded/thredded/blob/master/app/controllers/thredded/application_controller.rb#L18-L40).
564
+ The complete list can be found [here](https://github.com/thredded/thredded/blob/main/app/controllers/thredded/application_controller.rb#L18-L40).
555
565
 
556
566
  Currently, the default behaviour is to render an error message with an appropriate response code within the Thredded
557
567
  layout. You may want to override the handling for `Thredded::Errors::LoginRequired` to render a login form instead.
@@ -596,8 +606,6 @@ Rails.application.config.to_prepare do
596
606
  Thredded::ApplicationController.module_eval do
597
607
  # Require authentication to access the forums:
598
608
  before_action :thredded_require_login!
599
- # NB: in rails 4.2 you will need to change this to:
600
- # before_action { thredded_require_login! }
601
609
 
602
610
  # You may also want to render a login form after the
603
611
  # "Please sign in first" message:
@@ -809,4 +817,4 @@ docker-compose run web bundle exec rake
809
817
 
810
818
  The docker container uses PostgreSQL.
811
819
 
812
- [initializer]: https://github.com/thredded/thredded/blob/master/lib/generators/thredded/install/templates/initializer.rb
820
+ [initializer]: https://github.com/thredded/thredded/blob/main/lib/generators/thredded/install/templates/initializer.rb
@@ -34,8 +34,8 @@ module Thredded
34
34
  end
35
35
 
36
36
  def first_topic_content
37
- <<-MARKDOWN
38
- #{I18n.t('thredded.messageboard_first_topic.content', thredded_version: Thredded::VERSION)}
37
+ <<~MARKDOWN
38
+ #{I18n.t('thredded.messageboard_first_topic.content', thredded_version: Thredded::VERSION)}
39
39
  MARKDOWN
40
40
  end
41
41
  end
@@ -130,7 +130,7 @@ module Thredded
130
130
  end
131
131
  end
132
132
 
133
- def parse_names(text) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
133
+ def parse_names(text) # rubocop:disable Metrics/MethodLength
134
134
  result = []
135
135
  current = +''
136
136
  in_name = in_quoted = false
@@ -108,9 +108,9 @@ module Thredded
108
108
  # @param follow_reason ['manual', 'posted', 'mentioned', 'auto', nil]
109
109
  def topic_follow_reason_text(follow_reason)
110
110
  if follow_reason
111
- # rubocop:disable Metrics/LineLength
111
+ # rubocop:disable Layout/LineLength
112
112
  # i18n-tasks-use t('thredded.topics.following.manual') t('thredded.topics.following.posted') t('thredded.topics.following.mentioned') t('thredded.topics.following.auto')
113
- # rubocop:enable Metrics/LineLength
113
+ # rubocop:enable Layout/LineLength
114
114
  t("thredded.topics.following.#{follow_reason}")
115
115
  else
116
116
  t('thredded.topics.not_following')
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Thredded
4
4
  module UrlsHelper # rubocop:disable Metrics/ModuleLength
5
+ include Thredded::Engine.routes.url_helpers
6
+
5
7
  class << self
6
8
  include Thredded::Engine.routes.url_helpers
7
9
  include Thredded::UrlsHelper
@@ -11,13 +11,13 @@ module Thredded
11
11
  protected
12
12
 
13
13
  def mock_content(mention_users: [])
14
- <<-MARKDOWN
15
- Hey #{mention_users.map { |u| "@#{u}" } * ', '}!
16
- All of the basic [Markdown](https://kramdown.gettalong.org/quickref.html) formatting is supported (powered by [Kramdown](https://kramdown.gettalong.org)).
14
+ <<~MARKDOWN
15
+ Hey #{mention_users.map { |u| "@#{u}" } * ', '}!
16
+ All of the basic [Markdown](https://kramdown.gettalong.org/quickref.html) formatting is supported (powered by [Kramdown](https://kramdown.gettalong.org)).
17
17
 
18
- Additionally, Markdown is extended to support the following:
18
+ Additionally, Markdown is extended to support the following:
19
19
 
20
- #{Thredded::FormattingDemoContent.parts.join("\n")}
20
+ #{Thredded::FormattingDemoContent.parts.join("\n")}
21
21
  MARKDOWN
22
22
  end
23
23
 
@@ -11,7 +11,7 @@ module Thredded
11
11
  end
12
12
 
13
13
  def include?(slug)
14
- @words.include?(slug) || slug =~ PAGINATION_PATTERN
14
+ @words.include?(slug) || PAGINATION_PATTERN.match?(slug)
15
15
  end
16
16
  end
17
17
  end
@@ -8,7 +8,7 @@ module Thredded
8
8
  delegate :human_name, to: :notifier, prefix: true
9
9
 
10
10
  def self.detect_or_default(prefs, notifier)
11
- (prefs && prefs.find { |pref| pref.notifier_key == notifier.key }) || default(notifier)
11
+ (prefs&.find { |pref| pref.notifier_key == notifier.key }) || default(notifier)
12
12
  end
13
13
  end
14
14
 
@@ -26,8 +26,8 @@ module Thredded
26
26
  preloader = Thredded::Compat.association_preloader(
27
27
  records: owners_by_id.values, associations: [:first_post],
28
28
  scope: unscoped.where(<<~SQL.delete("\n"))
29
- #{posts_table_name}.created_at = (
30
- SELECT MAX(p2.created_at) from #{posts_table_name} p2 WHERE p2.postable_id = #{posts_table_name}.postable_id)
29
+ #{posts_table_name}.created_at = (
30
+ SELECT MAX(p2.created_at) from #{posts_table_name} p2 WHERE p2.postable_id = #{posts_table_name}.postable_id)
31
31
  SQL
32
32
  )
33
33
  preloader[0].preloaded_records.each do |post|
@@ -68,7 +68,7 @@ module Thredded
68
68
  def mark_as_unread(user)
69
69
  if previous_post.nil?
70
70
  read_state = postable.user_read_states.find_by(user_id: user.id)
71
- read_state.destroy if read_state
71
+ read_state&.destroy
72
72
  else
73
73
  postable.user_read_states.touch!(user.id, previous_post, overwrite_newer: true)
74
74
  end
@@ -33,12 +33,12 @@ module Thredded
33
33
  h[r.post.postable_id] = r.post.postable if r.post
34
34
  end
35
35
  next result if owners_by_id.empty?
36
- preloader = ActiveRecord::Associations::Preloader.new.preload(
37
- owners_by_id.values, :first_post,
38
- Thredded::Post.unscoped.where(<<~SQL.delete("\n"))
36
+ preloader = Thredded::Compat.association_preloader(
37
+ records: owners_by_id.values, associations: [:first_post],
38
+ scope: Thredded::Post.unscoped.where(<<~SQL.delete("\n"))
39
39
  #{posts_table_name}.created_at = (
40
40
  SELECT MAX(p2.created_at) from #{posts_table_name} p2 WHERE p2.postable_id = #{posts_table_name}.postable_id)
41
- SQL
41
+ SQL
42
42
  )
43
43
  preloader[0].preloaded_records.each do |post|
44
44
  topic = owners_by_id.delete(post.postable_id)
@@ -9,6 +9,7 @@ module Thredded
9
9
  :empty?,
10
10
  :[],
11
11
  :each,
12
+ :each_with_index,
12
13
  :map,
13
14
  :size,
14
15
  to: :@topic_views
@@ -13,13 +13,13 @@
13
13
  time_ago: time_ago(record.created_at)
14
14
  }
15
15
  %>
16
- <article class="thredded--post-moderation-record thredded--post-moderation-record-<%= record.moderation_state %>">
16
+ <article id="<%= dom_id(post) %>" class="thredded--post-moderation-record thredded--post-moderation-record-<%= record.moderation_state %>">
17
17
  <header class="thredded--post-moderation-record--header">
18
18
  <p class="thredded--post-moderation-record--moderation-state-notice">
19
19
  <% if record.approved? %>
20
- <%= t('thredded.moderation.post_approved_html', moderation_state_notice_args) %>
20
+ <%= t('thredded.moderation.post_approved_html', **moderation_state_notice_args) %>
21
21
  <% elsif record.blocked? %>
22
- <%= t('thredded.moderation.post_blocked_html', moderation_state_notice_args) %>
22
+ <%= t('thredded.moderation.post_blocked_html', **moderation_state_notice_args) %>
23
23
  <% end %>
24
24
  </p>
25
25
  <% if post && post.content != record.post_content %>
@@ -2,11 +2,11 @@
2
2
  <% resource_name = Thredded.user_class_name.demodulize.underscore %>
3
3
  <% if thredded_signed_in? %>
4
4
  <%= link_to main_app.send(:"destroy_#{resource_name}_session_path"), method: :delete do %>
5
- t('thredded.nav.sign_out')
5
+ <%= t('thredded.nav.sign_out') %>
6
6
  <% end %>
7
7
  <% else %>
8
8
  <%= link_to main_app.send(:"new_#{resource_name}_session_path") do %>
9
- t('thredded.nav.sign_in')
9
+ <%= t('thredded.nav.sign_in') %>
10
10
  <% end %>
11
11
  <% end %>
12
12
  </li>
data/bin/rubocop ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('rubocop', 'rubocop')
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # exit on first failure
4
+ set -e
5
+ for i in {1..10}
6
+ do
7
+ echo "#$i (rspec $1)"
8
+ bundle exec rspec --format d $1 --fail-fast
9
+ done
@@ -1,5 +1,5 @@
1
1
  # i18n-tasks finds and manages missing and unused translations
2
- # See https://github.com/glebm/i18n-tasks/blob/master/templates/config/i18n-tasks.yml
2
+ # See https://github.com/glebm/i18n-tasks/blob/main/templates/config/i18n-tasks.yml
3
3
 
4
4
  # Do not consider these keys missing:
5
5
  ignore_missing:
@@ -6,11 +6,11 @@ class UpgradeV07ToV08 < Thredded::BaseMigration
6
6
  def up
7
7
  closed_messageboards = Thredded::Messageboard.unscoped.where(closed: true).to_a
8
8
  if closed_messageboards.present?
9
- fail ActiveRecord::MigrationError, <<-TEXT
10
- There are #{closed_messageboards.length} closed Messageboards:
11
- #{closed_messageboards.map { |m| "#{m.name} (id=#{m.id})" }.join("\n")}
12
- Support for closed messageboards has been removed in thredded v0.8.0.
13
- Delete or un-close these messageboards and consider using the "paranoia" gem to support soft deletion instead.
9
+ fail ActiveRecord::MigrationError, <<~TEXT
10
+ There are #{closed_messageboards.length} closed Messageboards:
11
+ #{closed_messageboards.map { |m| "#{m.name} (id=#{m.id})" }.join("\n")}
12
+ Support for closed messageboards has been removed in thredded v0.8.0.
13
+ Delete or un-close these messageboards and consider using the "paranoia" gem to support soft deletion instead.
14
14
  TEXT
15
15
  end
16
16
  remove_index :thredded_messageboards, name: :index_thredded_messageboards_on_closed
@@ -124,7 +124,7 @@ Thredded.layout = 'thredded/application'
124
124
  # Change the HTML sanitization settings used by Thredded.
125
125
  # See the Sanitize docs for more information on the underlying library: https://github.com/rgrove/sanitize/#readme
126
126
  # E.g. to allow a custom element <custom-element>:
127
- # Thredded::ContentFormatter.whitelist[:elements] += %w(custom-element)
127
+ # Thredded::ContentFormatter.allowlist[:elements] += %w(custom-element)
128
128
 
129
129
  # ==> User autocompletion (Private messages and @-mentions)
130
130
  # Thredded.autocomplete_min_length = 2 lower to 1 if have 1-letter names -- increase if you want
@@ -9,7 +9,7 @@ module Thredded
9
9
  # @param [Arel::Nodes::Node] b integer node
10
10
  # @return [Arel::Nodes::Node] a / b
11
11
  def integer_division(engine, a, b)
12
- if engine.connection.adapter_name =~ /mysql|mariadb/i
12
+ if /mysql|mariadb/i.match?(engine.connection.adapter_name)
13
13
  Arel::Nodes::InfixOperation.new('DIV', a, b)
14
14
  else
15
15
  Arel::Nodes::Division.new(a, b)
@@ -15,7 +15,7 @@ module Thredded
15
15
 
16
16
  # @return [Integer, nil] the maximum number of codepoints that can be indexed for a primary key or index.
17
17
  def max_key_length
18
- return nil unless connection.adapter_name =~ /mysql|maria/i
18
+ return nil unless /mysql|maria/i.match?(connection.adapter_name)
19
19
  # Conservatively assume that innodb_large_prefix is **disabled**.
20
20
  # If it were enabled, the maximum key length would instead be 768 utf8mb4 characters.
21
21
  191
@@ -47,7 +47,8 @@ module Thredded
47
47
 
48
48
  ordered_keys.map do |cache_key|
49
49
  [keyed_collection[cache_key], cached_partials[cache_key] || rendered_partials.next.tap do |rendered|
50
- cached_partials[cache_key] = cache.write(cache_key, rendered, expires_in: expires_in)
50
+ cache.write(cache_key, rendered, expires_in: expires_in)
51
+ cached_partials[cache_key] = rendered
51
52
  end]
52
53
  end
53
54
  end
@@ -82,18 +83,29 @@ module Thredded
82
83
  else
83
84
  collection.each_slice(collection.size / num_threads).map do |slice|
84
85
  Thread.start do
85
- # `ActionView::PartialRenderer` mutates the contents of `opts[:locals]`, `opts[:locals][:as]` in particular:
86
- # https://github.com/rails/rails/blob/v6.0.2.1/actionview/lib/action_view/renderer/partial_renderer.rb#L379
87
- # https://github.com/rails/rails/blob/v6.0.2.1/actionview/lib/action_view/renderer/partial_renderer.rb#L348-L356
88
- opts[:locals] = opts[:locals].dup if opts[:locals]
89
86
  ActiveRecord::Base.connection_pool.with_connection do
90
- render_partials_serial(view_context.dup, slice, opts)
87
+ render_partials_serial(view_context.dup, slice, dup_opts(opts))
91
88
  end
92
89
  end
93
90
  end.flat_map(&:value)
94
91
  end
95
92
  end
96
93
 
94
+ # `ActionView::PartialRenderer` mutates the contents of `opts[:locals]`, `opts[:locals][:as]` in particular:
95
+ # https://github.com/rails/rails/blob/v6.0.2.1/actionview/lib/action_view/renderer/partial_renderer.rb#L379
96
+ # https://github.com/rails/rails/blob/v6.0.2.1/actionview/lib/action_view/renderer/partial_renderer.rb#L348-L356
97
+ def dup_opts(opts)
98
+ if opts[:locals]
99
+ opts = opts.dup
100
+ # sometimes have a thread safe :users_provider, preserve that for performance reasons
101
+ # hence not doing a deep_dup
102
+ opts[:locals] = opts[:locals].dup
103
+ opts
104
+ else
105
+ opts.dup
106
+ end
107
+ end
108
+
97
109
  if Thredded::Compat.rails_gte_61?
98
110
  # @param [Array<Object>] collection
99
111
  # @param [Hash] opts
@@ -121,42 +133,20 @@ module Thredded
121
133
  view.combined_fragment_cache_key(key)
122
134
  end
123
135
 
124
- if Thredded::Compat.rails_gte_60?
125
- def cache_fragment_name(view, key, virtual_path:, digest_path:)
126
- if Thredded::Compat.rails_gte_61?
127
- view.cache_fragment_name(key, digest_path: digest_path)
128
- else
129
- view.cache_fragment_name(key, virtual_path: virtual_path, digest_path: digest_path)
130
- end
131
- end
132
-
133
- def digest_path_from_template(view, template)
134
- view.digest_path_from_template(template)
135
- end
136
-
137
- def render_partial(partial_renderer, view_context, opts)
138
- partial_renderer.render(view_context, opts, nil).body
139
- end
140
- else
141
- def cache_fragment_name(_view, key, virtual_path:, digest_path:)
142
- if digest_path
143
- ["#{virtual_path}:#{digest_path}", key]
144
- else
145
- [virtual_path, key]
146
- end
136
+ def cache_fragment_name(view, key, virtual_path:, digest_path:)
137
+ if Thredded::Compat.rails_gte_61?
138
+ view.cache_fragment_name(key, digest_path: digest_path)
139
+ else
140
+ view.cache_fragment_name(key, virtual_path: virtual_path, digest_path: digest_path)
147
141
  end
142
+ end
148
143
 
149
- def digest_path_from_template(view, template)
150
- ActionView::Digestor.digest(
151
- name: template.virtual_path,
152
- finder: @lookup_context,
153
- dependencies: view.view_cache_dependencies
154
- ).presence
155
- end
144
+ def digest_path_from_template(view, template)
145
+ view.digest_path_from_template(template)
146
+ end
156
147
 
157
- def render_partial(partial_renderer, view_context, opts)
158
- partial_renderer.render(view_context, opts, nil)
159
- end
148
+ def render_partial(partial_renderer, view_context, opts)
149
+ partial_renderer.render(view_context, opts, nil).body
160
150
  end
161
151
  end
162
152
  end
@@ -3,12 +3,6 @@
3
3
  module Thredded
4
4
  module Compat
5
5
  class << self
6
- # @api private
7
- def rails_gte_60?
8
- @rails_gte_60 = (Rails.gem_version >= Gem::Version.new('6.0.0')) if @rails_gte_60.nil?
9
- @rails_gte_60
10
- end
11
-
12
6
  # @api private
13
7
  def rails_gte_61?
14
8
  @rails_gte_61 = (Rails.gem_version >= Gem::Version.new('6.1.0')) if @rails_gte_61.nil?
@@ -4,8 +4,19 @@ module Thredded
4
4
  # Generates HTML from content source.
5
5
  class ContentFormatter
6
6
  class << self
7
- # Sanitization whitelist options.
8
- attr_accessor :whitelist
7
+ # Sanitization allowlist options.
8
+ attr_accessor :allowlist
9
+
10
+ # TODO: v2.0: drop alias and just use allowlist
11
+ # @deprecated use allowlist
12
+ def whitelist
13
+ ActiveSupport::Deprecation.warn(<<~MESSAGE.squish)
14
+ ContentFormatter.whitelist is deprecated and will be removed in Thredded 2.0.
15
+ Use ContentFormatter.allowlist instead
16
+ MESSAGE
17
+
18
+ allowlist
19
+ end
9
20
 
10
21
  # Filters that run before processing the markup.
11
22
  # input: markup, output: markup.
@@ -26,16 +37,20 @@ module Thredded
26
37
  # Filters that run after sanitization
27
38
  # input: sanitized html, output: html
28
39
  attr_accessor :after_sanitization_filters
40
+
41
+ def sanitization_filter_allowlist_config
42
+ HTML::Pipeline::SanitizationFilter::ALLOWLIST
43
+ end
29
44
  end
30
45
 
31
- self.whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
32
- elements: HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w[abbr iframe span figure figcaption],
33
- transformers: HTML::Pipeline::SanitizationFilter::WHITELIST[:transformers] + [
46
+ self.allowlist = sanitization_filter_allowlist_config.deep_merge(
47
+ elements: sanitization_filter_allowlist_config[:elements] + %w[abbr iframe span figure figcaption],
48
+ transformers: sanitization_filter_allowlist_config[:transformers] + [
34
49
  ->(env) {
35
50
  next unless env[:node_name] == 'a'
36
51
  a_tag = env[:node]
37
52
  a_tag['href'] ||= '#'
38
- if a_tag['href'] =~ %r{^(?:[a-z]+:)?//}
53
+ if %r{^(?:[a-z]+:)?//}.match?(a_tag['href'])
39
54
  a_tag['target'] = '_blank'
40
55
  a_tag['rel'] = 'nofollow noopener'
41
56
  end
@@ -49,7 +64,7 @@ module Thredded
49
64
  'img' => %w[src longdesc class],
50
65
  'th' => %w[style],
51
66
  'td' => %w[style],
52
- :all => HTML::Pipeline::SanitizationFilter::WHITELIST[:attributes][:all] +
67
+ :all => sanitization_filter_allowlist_config[:attributes][:all] +
53
68
  %w[aria-expanded aria-label aria-labelledby aria-live aria-hidden aria-pressed role],
54
69
  },
55
70
  css: {
@@ -136,7 +151,7 @@ module Thredded
136
151
  def content_pipeline_options
137
152
  {
138
153
  asset_root: Rails.application.config.action_controller.asset_host || '',
139
- whitelist: ContentFormatter.whitelist
154
+ allowlist: ContentFormatter.allowlist
140
155
  }
141
156
  end
142
157
  end
@@ -10,11 +10,7 @@ module Thredded
10
10
  verbose_was = ActiveRecord::Migration.verbose
11
11
  ActiveRecord::Migration.verbose = !quiet
12
12
  migrate =
13
- if Thredded::Compat.rails_gte_60?
14
- -> { ActiveRecord::MigrationContext.new(paths, ActiveRecord::SchemaMigration).migrate(nil, &filter) }
15
- else # Rails 5.2
16
- -> { ActiveRecord::MigrationContext.new(paths).migrate(nil, &filter) }
17
- end
13
+ -> { ActiveRecord::MigrationContext.new(paths, ActiveRecord::SchemaMigration).migrate(nil, &filter) }
18
14
  if quiet
19
15
  silence_active_record(&migrate)
20
16
  else
@@ -59,7 +55,7 @@ module Thredded
59
55
  silence_active_record do
60
56
  ActiveRecord::Base.transaction do
61
57
  statements.each do |statement|
62
- connection.execute(statement) unless statement =~ /(BEGIN TRANSACTION|COMMIT)/
58
+ connection.execute(statement) unless /(BEGIN TRANSACTION|COMMIT)/.match?(statement)
63
59
  end
64
60
  end
65
61
  end
@@ -12,7 +12,7 @@ module Thredded
12
12
 
13
13
  config.to_prepare do
14
14
  Thredded::AllViewHooks.reset_instance!
15
- Thredded.user_class.send(:include, Thredded::UserExtender) if Thredded.user_class
15
+ Thredded.user_class&.send(:include, Thredded::UserExtender)
16
16
  end
17
17
 
18
18
  initializer 'thredded.setup_assets' do
@@ -7,35 +7,35 @@ module Thredded
7
7
  attr_accessor :parts
8
8
  end
9
9
  self.parts = [
10
- <<-'MARKDOWN',
11
- #### Spoilers
10
+ <<~'MARKDOWN',
11
+ #### Spoilers
12
12
 
13
- Use `<spoiler></spoiler>` tags to create spoiler boxes like this one:
13
+ Use `<spoiler></spoiler>` tags to create spoiler boxes like this one:
14
14
 
15
- <spoiler>
16
- Harry Potter books are better than the movies.
15
+ <spoiler>
16
+ Harry Potter books are better than the movies.
17
17
 
18
- ![nyancat](https://storage.googleapis.com/glebm-stuff/nyancat.gif)
18
+ ![nyancat](https://storage.googleapis.com/glebm-stuff/nyancat.gif)
19
19
 
20
- https://www.youtube.com/watch?v=5lBBUPVuusM
21
- </spoiler>
20
+ https://www.youtube.com/watch?v=5lBBUPVuusM
21
+ </spoiler>
22
22
 
23
- #### Oneboxes
23
+ #### Oneboxes
24
24
 
25
- URLs of supported resources are replaced with boxes like these:
25
+ URLs of supported resources are replaced with boxes like these:
26
26
 
27
- **Twitter** `https://twitter.com/thredded/status/838824533477982209`:
28
- https://twitter.com/thredded/status/838824533477982209
29
- **StackExchange** `http://codegolf.stackexchange.com/questions/45701`:
30
- http://codegolf.stackexchange.com/questions/45701
31
- **Amazon** `https://www.amazon.co.uk/dp/0521797071`:
32
- https://www.amazon.co.uk/dp/0521797071
33
- **YouTube** `https://www.youtube.com/watch?v=1QP7elXwpLw`:
34
- https://www.youtube.com/watch?v=1QP7elXwpLw
35
- **Google Maps** `https://goo.gl/maps/R6nj3Qwf2LR2`:
36
- https://goo.gl/maps/R6nj3Qwf2LR2
27
+ **Twitter** `https://twitter.com/thredded/status/838824533477982209`:
28
+ https://twitter.com/thredded/status/838824533477982209
29
+ **StackExchange** `http://codegolf.stackexchange.com/questions/45701`:
30
+ http://codegolf.stackexchange.com/questions/45701
31
+ **Amazon** `https://www.amazon.co.uk/dp/0521797071`:
32
+ https://www.amazon.co.uk/dp/0521797071
33
+ **YouTube** `https://www.youtube.com/watch?v=1QP7elXwpLw`:
34
+ https://www.youtube.com/watch?v=1QP7elXwpLw
35
+ **Google Maps** `https://goo.gl/maps/R6nj3Qwf2LR2`:
36
+ https://goo.gl/maps/R6nj3Qwf2LR2
37
37
 
38
- Many more resources are [supported](https://github.com/discourse/onebox/tree/master/lib/onebox/engine). Powered by the [onebox](https://github.com/discourse/onebox) library.
38
+ Many more resources are [supported](https://github.com/discourse/onebox/tree/main/lib/onebox/engine). Powered by the [onebox](https://github.com/discourse/onebox) library.
39
39
  MARKDOWN
40
40
  ]
41
41
  end
@@ -47,7 +47,7 @@ module Thredded
47
47
  return if names.blank? || @users_provider.nil?
48
48
  @users_provider.call(names, @users_provider_scope).each do |user|
49
49
  name = user.send(Thredded.user_name_column)
50
- maybe_quoted_name = name =~ /[., ()]/ ? %("#{name}") : name
50
+ maybe_quoted_name = /[., ()]/.match?(name) ? %("#{name}") : name
51
51
  url = Thredded.user_path(@view_context, user)
52
52
  text_node_html.gsub!(
53
53
  /(^|[\s>])(@#{Regexp.escape maybe_quoted_name})([^a-z\d]|$)/i,
@@ -24,7 +24,7 @@ module Thredded
24
24
  next unless env[:node_name] == 'a'
25
25
  a_tag = env[:node]
26
26
  a_tag['href'] ||= '#'
27
- if a_tag['href'] =~ %r{^(?:[a-z]+:)?//}
27
+ if %r{^(?:[a-z]+:)?//}.match?(a_tag['href'])
28
28
  a_tag['target'] = '_blank'
29
29
  a_tag['rel'] = 'nofollow noopener'
30
30
  else
@@ -68,9 +68,9 @@ module Thredded
68
68
 
69
69
  def render_onebox(url)
70
70
  preview = Onebox.preview(url, onebox_options(url))
71
- if context[:onebox_placeholders]
71
+ if context[:onebox_placeholders] && (placeholder_html = preview.placeholder_html.presence)
72
72
  %(<p><a href="#{ERB::Util.html_escape(url)}" target="_blank" rel="nofollow noopener">) \
73
- "#{preview.placeholder_html}</a></p>"
73
+ "#{placeholder_html}</a></p>"
74
74
  else
75
75
  preview.to_s.strip
76
76
  end
@@ -40,7 +40,7 @@ module Thredded
40
40
  end
41
41
 
42
42
  def node_name?(node, node_name)
43
- node && node.node_name && node.node_name.casecmp(node_name).zero?
43
+ node&.node_name && node.node_name.casecmp(node_name).zero?
44
44
  end
45
45
  end
46
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thredded
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Oliveira
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-02-26 00:00:00.000000000 Z
12
+ date: 2023-02-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: active_record_union
@@ -127,20 +127,14 @@ dependencies:
127
127
  name: rails
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - ">="
131
- - !ruby/object:Gem::Version
132
- version: 4.2.10
133
- - - "!="
130
+ - - ">"
134
131
  - !ruby/object:Gem::Version
135
132
  version: 6.0.0.rc2
136
133
  type: :runtime
137
134
  prerelease: false
138
135
  version_requirements: !ruby/object:Gem::Requirement
139
136
  requirements:
140
- - - ">="
141
- - !ruby/object:Gem::Version
142
- version: 4.2.10
143
- - - "!="
137
+ - - ">"
144
138
  - !ruby/object:Gem::Version
145
139
  version: 6.0.0.rc2
146
140
  - !ruby/object:Gem::Dependency
@@ -163,14 +157,14 @@ dependencies:
163
157
  requirements:
164
158
  - - ">="
165
159
  - !ruby/object:Gem::Version
166
- version: '0'
160
+ version: 2.14.1
167
161
  type: :runtime
168
162
  prerelease: false
169
163
  version_requirements: !ruby/object:Gem::Requirement
170
164
  requirements:
171
165
  - - ">="
172
166
  - !ruby/object:Gem::Version
173
- version: '0'
167
+ version: 2.14.1
174
168
  - !ruby/object:Gem::Dependency
175
169
  name: kramdown
176
170
  requirement: !ruby/object:Gem::Requirement
@@ -311,6 +305,20 @@ dependencies:
311
305
  - - "~>"
312
306
  - !ruby/object:Gem::Version
313
307
  version: '3.0'
308
+ - !ruby/object:Gem::Dependency
309
+ name: capybara-screenshot
310
+ requirement: !ruby/object:Gem::Requirement
311
+ requirements:
312
+ - - ">="
313
+ - !ruby/object:Gem::Version
314
+ version: '0'
315
+ type: :development
316
+ prerelease: false
317
+ version_requirements: !ruby/object:Gem::Requirement
318
+ requirements:
319
+ - - ">="
320
+ - !ruby/object:Gem::Version
321
+ version: '0'
314
322
  - !ruby/object:Gem::Dependency
315
323
  name: cuprite
316
324
  requirement: !ruby/object:Gem::Requirement
@@ -620,7 +628,7 @@ dependencies:
620
628
  - !ruby/object:Gem::Version
621
629
  version: '0'
622
630
  description: |-
623
- The best Rails 4.2+ forums engine ever. Its goal is to be as simple and feature rich as possible.
631
+ The best Rails 6.0+ forums engine ever. Its goal is to be as simple and feature rich as possible.
624
632
  Thredded works with SQLite, MySQL (v5.6.4+), and PostgreSQL. See the demo at https://thredded.org/.
625
633
  email:
626
634
  - joel@thredded.com
@@ -970,6 +978,8 @@ files:
970
978
  - app/views/thredded/users/_posts.html.erb
971
979
  - bin/create_migration_fixture
972
980
  - bin/rails
981
+ - bin/rubocop
982
+ - bin/run_specs_repeatedly
973
983
  - config/i18n-tasks.yml
974
984
  - config/locales/de.yml
975
985
  - config/locales/en.yml
@@ -1019,7 +1029,6 @@ files:
1019
1029
  - lib/thredded/html_pipeline/spoiler_tag_filter.rb
1020
1030
  - lib/thredded/html_pipeline/utils.rb
1021
1031
  - lib/thredded/html_pipeline/wrap_iframes_filter.rb
1022
- - lib/thredded/rails_lt_5_2_arel_case_node.rb
1023
1032
  - lib/thredded/users_provider.rb
1024
1033
  - lib/thredded/version.rb
1025
1034
  - lib/thredded/view_hooks/config.rb
@@ -1049,7 +1058,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1049
1058
  - !ruby/object:Gem::Version
1050
1059
  version: '0'
1051
1060
  requirements: []
1052
- rubygems_version: 3.1.6
1061
+ rubygems_version: 3.2.32
1053
1062
  signing_key:
1054
1063
  specification_version: 4
1055
1064
  summary: The best Rails forums engine ever.
@@ -1,119 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Arel
4
- module Nodes
5
- class Case < Arel::Nodes::Node
6
- include Arel::OrderPredications
7
- include Arel::Predications
8
- include Arel::AliasPredication
9
-
10
- attr_accessor :case, :conditions, :default
11
-
12
- def initialize(expression = nil, default = nil)
13
- @case = expression
14
- @conditions = []
15
- @default = default
16
- end
17
-
18
- def when(condition, expression = nil)
19
- @conditions << When.new(Nodes.build_quoted(condition), expression)
20
- self
21
- end
22
-
23
- def then(expression)
24
- @conditions.last.right = Nodes.build_quoted(expression)
25
- self
26
- end
27
-
28
- def else(expression)
29
- @default = Else.new Nodes.build_quoted(expression)
30
- self
31
- end
32
-
33
- def initialize_copy(other)
34
- super
35
- @case = @case.clone if @case
36
- @conditions = @conditions.map(&:clone)
37
- @default = @default.clone if @default
38
- end
39
-
40
- def hash
41
- [@case, @conditions, @default].hash
42
- end
43
-
44
- def eql?(other)
45
- self.class == other.class &&
46
- self.case == other.case &&
47
- conditions == other.conditions &&
48
- default == other.default
49
- end
50
-
51
- alias == eql?
52
- end
53
-
54
- class When < Binary
55
- end
56
-
57
- class Else < Unary
58
- end
59
- end
60
- end
61
-
62
- module Arel
63
- module Visitors
64
- class DepthFirst < Arel::Visitors::Visitor
65
- alias visit_Arel_Nodes_Else unary
66
-
67
- def visit_Arel_Nodes_Case(o) # rubocop:disable Naming/MethodName
68
- visit o.case
69
- visit o.conditions
70
- visit o.default
71
- end
72
-
73
- alias visit_Arel_Nodes_When binary
74
- end
75
- end
76
- end
77
-
78
- module Arel
79
- module Predications
80
- def when(right)
81
- Nodes::Case.new(self).when quoted_node(right)
82
- end
83
- end
84
- end
85
-
86
- module Arel
87
- module Visitors
88
- class ToSql < Arel::Visitors::Reduce
89
- def visit_Arel_Nodes_Case(o, collector) # rubocop:disable Naming/MethodName
90
- collector << 'CASE '
91
- if o.case
92
- visit o.case, collector
93
- collector << ' '
94
- end
95
- o.conditions.each do |condition|
96
- visit condition, collector
97
- collector << ' '
98
- end
99
- if o.default
100
- visit o.default, collector
101
- collector << ' '
102
- end
103
- collector << 'END'
104
- end
105
-
106
- def visit_Arel_Nodes_When(o, collector) # rubocop:disable Naming/MethodName
107
- collector << 'WHEN '
108
- visit o.left, collector
109
- collector << ' THEN '
110
- visit o.right, collector
111
- end
112
-
113
- def visit_Arel_Nodes_Else(o, collector) # rubocop:disable Naming/MethodName
114
- collector << 'ELSE '
115
- visit o.expr, collector
116
- end
117
- end
118
- end
119
- end