thredded 0.16.11 → 0.16.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 741784943db6fb342733ffb31a72839b04f50c11f73800ac6ef12e4a69eb61a8
4
- data.tar.gz: de850a7928e05f2d97990b918fe5fddf684859087fda119e6511f344ea385fc8
3
+ metadata.gz: dd9dbf548a2ae13d2c77b2a49e4022af13b17e5df084dc22baf370e37d7b1a2f
4
+ data.tar.gz: 6f21adfcf7f040ab397637f03f47df3c4f9f15d3956dfd4fb3a2a09b9a871ddd
5
5
  SHA512:
6
- metadata.gz: bf23cc3d905ba1ab460bab9a0fd40babfb4bdccac806bc299a3100cf9550b7d80b5d0c948cfe7493997fef773a3260cca3449ff46360cd3ff66c2a63dd3c03a6
7
- data.tar.gz: 155e6a9079c65c33008d91a9bf5def441dd7f5d78021d6656ee2d2625248e6d0bf01f773107b871000b917e9fbd39d5a0bc653f18953807d37a4a8779c223af7
6
+ metadata.gz: 80692ab8668d1fdbcc60309774f2dcab3e8a6893b1a78892e9507ad4a9f70d9d91d174ce9277058622faba6d562c3187f4cf6dfa2aec92c97a9863a32b858b4f
7
+ data.tar.gz: dd5c3253a6df6f69512eb5acd778beb0ef7370be4b53b30fb03c865b6669af3e7e5217cc46e5feaf4695c1354cd9b748743b05f66edc94eff5c55a8ef810b886
data/README.md CHANGED
@@ -95,7 +95,7 @@ Then, see the rest of this Readme for more information about using and customizi
95
95
  Add the gem to your Gemfile:
96
96
 
97
97
  ```ruby
98
- gem 'thredded', '~> 0.16.11'
98
+ gem 'thredded', '~> 0.16.12'
99
99
  ```
100
100
 
101
101
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -19,6 +19,8 @@ module Thredded
19
19
  @post_moderation_records = accessible_post_moderation_records
20
20
  .order(created_at: :desc)
21
21
  .send(Kaminari.config.page_method_name, current_page)
22
+ .preload(:messageboard, :post_user, :moderator, post: :postable)
23
+ .preload_first_topic_post
22
24
  end
23
25
 
24
26
  def activity
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module ModerationHelper
5
+ include ::Thredded::RenderHelper
6
+
7
+ # @param records [Array<Thredded::PostModerationRecord>]
8
+ def render_post_moderation_records(records)
9
+ records_with_post_contents = render_collection_to_strings_with_cache(
10
+ partial: 'thredded/moderation/post_moderation_record_content',
11
+ collection: records, as: :post_moderation_record, expires_in: 1.week,
12
+ locals: {
13
+ options: {
14
+ users_provider: ::Thredded::UsersProviderWithCache.new
15
+ }
16
+ }
17
+ )
18
+ render partial: 'thredded/moderation/post_moderation_record',
19
+ collection: records_with_post_contents,
20
+ as: :record_and_post_content
21
+ end
22
+ end
23
+ end
@@ -21,13 +21,20 @@ module Thredded
21
21
  scope :preload_first_topic_post, -> {
22
22
  posts_table_name = quoted_table_name
23
23
  result = all
24
- ActiveRecord::Associations::Preloader.new.preload(
25
- result.map(&:postable), :first_post,
24
+ owners_by_id = result.each_with_object({}) { |r, h| h[r.postable_id] = r.postable }
25
+ next result if owners_by_id.empty?
26
+ preloader = ActiveRecord::Associations::Preloader.new.preload(
27
+ owners_by_id.values, :first_post,
26
28
  unscoped.where(<<~SQL.delete("\n"))
27
29
  #{posts_table_name}.created_at = (
28
30
  SELECT MAX(p2.created_at) from #{posts_table_name} p2 WHERE p2.postable_id = #{posts_table_name}.postable_id)
29
31
  SQL
30
32
  )
33
+ preloader[0].preloaded_records.each do |post|
34
+ topic = owners_by_id.delete(post.postable_id)
35
+ next unless topic
36
+ topic.association(:first_post).target = post
37
+ end
31
38
  result
32
39
  }
33
40
 
@@ -14,7 +14,9 @@ module Thredded
14
14
  scope :order_recently_posted_first, -> { order(last_post_at: :desc, id: :desc) }
15
15
  scope :on_page, ->(page_num) { page(page_num) }
16
16
 
17
- validates :hash_id, presence: true, uniqueness: true
17
+ validates :hash_id,
18
+ presence: true,
19
+ uniqueness: { case_sensitive: true }
18
20
  validates :posts_count, numericality: true
19
21
 
20
22
  validates :title, presence: true, length: { within: Thredded.topic_title_length_range }
@@ -53,9 +55,19 @@ module Thredded
53
55
  topics = arel_table
54
56
  reads_class = reflect_on_association(:user_read_states).klass
55
57
  reads = reads_class.arel_table
56
- joins(topics.join(reads, Arel::Nodes::OuterJoin)
57
- .on(topics[:id].eq(reads[:postable_id]).and(reads[:user_id].eq(user.id))).join_sources)
58
- .merge(reads_class.where(reads[:id].eq(nil).or(reads[:unread_posts_count].not_eq(0))))
58
+
59
+ joins_reads =
60
+ topics.outer_join(reads)
61
+ .on(topics[:id].eq(reads[:postable_id]).and(reads[:user_id].eq(user.id))).join_sources
62
+
63
+ unread_scope = reads_class.where(reads[:id].eq(nil).or(reads[:unread_posts_count].not_eq(0)))
64
+
65
+ # Work around https://github.com/rails/rails/issues/36761
66
+ if Thredded.rails_gte_600_rc_2?
67
+ merge(unread_scope).joins(joins_reads)
68
+ else
69
+ joins(joins_reads).merge(unread_scope)
70
+ end
59
71
  end
60
72
 
61
73
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- class Messageboard < ActiveRecord::Base
4
+ class Messageboard < ActiveRecord::Base # rubocop:disable Metrics/ClassLength
5
5
  extend FriendlyId
6
6
  friendly_id :slug_candidates,
7
7
  use: %i[slugged reserved],
@@ -21,7 +21,10 @@ module Thredded
21
21
  ]
22
22
  )
23
23
 
24
- validates :name, uniqueness: true, length: { within: Thredded.messageboard_name_length_range }, presence: true
24
+ validates :name,
25
+ uniqueness: { case_sensitive: false },
26
+ length: { within: Thredded.messageboard_name_length_range },
27
+ presence: true
25
28
  validates :topics_count, numericality: true
26
29
  validates :position, presence: true, on: :update
27
30
  before_save :ensure_position
@@ -128,14 +131,28 @@ module Thredded
128
131
  messageboards = arel_table
129
132
  read_states = Thredded::UserTopicReadState.arel_table
130
133
  topics = topics_scope.arel_table
131
- joins(:topics).merge(topics_scope).joins(
132
- messageboards.outer_join(read_states).on(
133
- messageboards[:id].eq(read_states[:messageboard_id])
134
- .and(read_states[:postable_id].eq(topics[:id]))
135
- .and(read_states[:user_id].eq(user.id))
136
- .and(read_states[:unread_posts_count].eq(0))
137
- ).join_sources
138
- ).group(messageboards[:id]).pluck(
134
+
135
+ read_states_join_cond =
136
+ messageboards[:id].eq(read_states[:messageboard_id])
137
+ .and(read_states[:postable_id].eq(topics[:id]))
138
+ .and(read_states[:user_id].eq(user.id))
139
+ .and(read_states[:unread_posts_count].eq(0))
140
+
141
+ scope =
142
+ # Work around https://github.com/rails/rails/issues/36761
143
+ if Thredded.rails_gte_600_rc_2?
144
+ merge(topics_scope).joins(
145
+ messageboards.join(topics)
146
+ .on(topics[:messageboard_id].eq(messageboards[:id]))
147
+ .outer_join(read_states).on(read_states_join_cond).join_sources
148
+ )
149
+ else
150
+ joins(:topics).merge(topics_scope).joins(
151
+ messageboards.outer_join(read_states).on(read_states_join_cond).join_sources
152
+ )
153
+ end
154
+
155
+ scope.group(messageboards[:id]).pluck(
139
156
  :id,
140
157
  Arel::Nodes::Subtraction.new(topics[:id].count, read_states[:id].count)
141
158
  ).to_h
@@ -8,7 +8,9 @@ module Thredded
8
8
  dependent: :nullify
9
9
 
10
10
  scope :ordered, -> { order(position: :asc, id: :asc) }
11
- validates :name, presence: true, uniqueness: true
11
+ validates :name,
12
+ presence: true,
13
+ uniqueness: { case_sensitive: false }
12
14
  validates :position, presence: true, on: :update
13
15
  before_save :ensure_position
14
16
 
@@ -27,8 +27,33 @@ module Thredded
27
27
  record.errors.add attr, "Post moderation_state is already #{value}" if record.previous_moderation_state == value
28
28
  end
29
29
 
30
+ scope :preload_first_topic_post, -> {
31
+ posts_table_name = Thredded::Post.quoted_table_name
32
+ result = all
33
+ owners_by_id = result.each_with_object({}) { |r, h| h[r.post.postable_id] = r.post.postable }
34
+ next result if owners_by_id.empty?
35
+ preloader = ActiveRecord::Associations::Preloader.new.preload(
36
+ owners_by_id.values, :first_post,
37
+ Thredded::Post.unscoped.where(<<~SQL.delete("\n"))
38
+ #{posts_table_name}.created_at = (
39
+ SELECT MAX(p2.created_at) from #{posts_table_name} p2 WHERE p2.postable_id = #{posts_table_name}.postable_id)
40
+ SQL
41
+ )
42
+ preloader[0].preloaded_records.each do |post|
43
+ topic = owners_by_id.delete(post.postable_id)
44
+ next unless topic
45
+ topic.association(:first_post).target = post
46
+ end
47
+ result
48
+ }
49
+
30
50
  paginates_per Thredded.posts_per_page
31
51
 
52
+ # @return [ActiveRecord::Relation<Thredded.user_class>] users that can read the moderated post.
53
+ def post_readers
54
+ Thredded.user_class.thredded_messageboards_readers([messageboard])
55
+ end
56
+
32
57
  # @param [Thredded.user_class] moderator
33
58
  # @param [Thredded::Post] post
34
59
  # @param [Symbol, String] previous_moderation_state
@@ -5,7 +5,9 @@ module Thredded
5
5
  include Thredded::ModerationState
6
6
 
7
7
  belongs_to :user, class_name: Thredded.user_class_name, inverse_of: :thredded_user_detail
8
- validates :user_id, uniqueness: true, **(Thredded.rails_gte_51? ? {} : { presence: true })
8
+ validates :user_id,
9
+ uniqueness: { case_sensitive: true },
10
+ **(Thredded.rails_gte_51? ? {} : { presence: true })
9
11
 
10
12
  with_options foreign_key: :user_id, primary_key: :user_id, inverse_of: :user_detail, dependent: :nullify do
11
13
  has_many :topics, class_name: 'Thredded::Topic'
@@ -1,5 +1,9 @@
1
1
  <%
2
- record = post_moderation_record
2
+ if local_assigns.key?(:record_and_post_content)
3
+ record, post_content = record_and_post_content
4
+ else
5
+ record = post_moderation_record
6
+ end
3
7
  post = record.post
4
8
  if post
5
9
  post_view = Thredded::PostView.new(post, policy(post))
@@ -34,12 +38,14 @@
34
38
  <% end %>
35
39
  <% end %>
36
40
  <% if post %>
37
- <%= render 'thredded/posts_common/header_with_user_and_topic', post: post_view, post_user_link: post_user_link %>
41
+ <%= render 'thredded/posts_common/header_with_user_and_topic',
42
+ post: post_view, user: record.post_user, post_user_link: post_user_link %>
38
43
  <% else %>
39
44
  <header><h2 class="thredded--post--user"><%= post_user_link %></h2></header>
40
45
  <% end %>
41
46
  <div class="thredded--post--content">
42
- <%= Thredded::ContentFormatter.new(self).format_content(record.post_content) %>
47
+ <%= post_content ||
48
+ render('thredded/moderation/post_moderation_record_content', post_moderation_record: record) %>
43
49
  </div>
44
50
  </article>
45
51
  <%= render 'post_moderation_actions', post: post if post %>
@@ -0,0 +1,3 @@
1
+ <%= Thredded::ContentFormatter.
2
+ new(self, (local_assigns[:options] || {}).update(users_provider_scope: post_moderation_record.post_readers)).
3
+ format_content(post_moderation_record.post_content) %>
@@ -7,7 +7,7 @@
7
7
  <h1><%= t 'thredded.recent_activity' %></h1>
8
8
  <% if @last_moderated_record %>
9
9
  <div class="thredded--moderated-notice">
10
- <%= render 'post_moderation_record', post_moderation_record: @last_moderated_record %>
10
+ <%= render_post_moderation_records([@last_moderated_record]) %>
11
11
  </div>
12
12
  <% end %>
13
13
  <% if @posts.present? %>
@@ -6,7 +6,7 @@
6
6
  <%= thredded_page do %>
7
7
  <section class="thredded--main-section">
8
8
  <% if @post_moderation_records.present? %>
9
- <%= render partial: 'post_moderation_record', collection: @post_moderation_records %>
9
+ <%= render_post_moderation_records @post_moderation_records %>
10
10
  <%= paginate @post_moderation_records %>
11
11
  <% end %>
12
12
  </section>
@@ -6,7 +6,7 @@
6
6
  <section class="thredded--main-section">
7
7
  <% if @last_moderated_record %>
8
8
  <div class="thredded--moderated-notice">
9
- <%= render 'post_moderation_record', post_moderation_record: @last_moderated_record %>
9
+ <%= render_post_moderation_records([@last_moderated_record]) %>
10
10
  </div>
11
11
  <% end %>
12
12
  <% if @posts.present? %>
@@ -1,9 +1,11 @@
1
1
  <%# @param post [Thredded::PostView] %>
2
+ <%# @param user [Thredded.user_class] optional %>
2
3
  <%# @param post_user_link [String] optional %>
3
4
  <% topic = post.to_model.postable %>
4
- <% post_user_link ||= user_link(post.user) %>
5
+ <% user ||= post.user %>
6
+ <% post_user_link ||= user_link(user) %>
5
7
  <header>
6
- <%= image_tag post.avatar_url, class: 'thredded--post--avatar' if post.user %>
8
+ <%= image_tag Thredded.avatar_url.call(user), class: 'thredded--post--avatar' if user %>
7
9
  <h2 class="thredded--post--user-and-topic">
8
10
  <%=
9
11
  topic_link = link_to(topic.title, post.permalink_path)
@@ -251,6 +251,13 @@ module Thredded # rubocop:disable Metrics/ModuleLength
251
251
  @rails_gte_51
252
252
  end
253
253
 
254
+ # @api private
255
+ # Mainly to work around https://github.com/rails/rails/issues/36761
256
+ def rails_gte_600_rc_2?
257
+ @rails_gte_600_rc_2 = (Rails.gem_version >= Gem::Version.new('6.0.0.rc2')) if @rails_gte_600_rc_2.nil?
258
+ @rails_gte_600_rc_2
259
+ end
260
+
254
261
  # @api private
255
262
  def rails_supports_csp_nonce?
256
263
  @rails_supports_csp_nonce = (Rails.gem_version >= Gem::Version.new('5.2.0')) if @rails_supports_csp_nonce.nil?
@@ -9,13 +9,14 @@ module Thredded
9
9
  def migrate(paths:, quiet:, &filter)
10
10
  verbose_was = ActiveRecord::Migration.verbose
11
11
  ActiveRecord::Migration.verbose = !quiet
12
- migrate = -> {
13
- if Rails::VERSION::STRING >= '5.2'
14
- ActiveRecord::MigrationContext.new(paths).migrate(nil, &filter)
12
+ migrate =
13
+ if Rails.gem_version >= Gem::Version.new('6.0.0.rc2')
14
+ -> { ActiveRecord::MigrationContext.new(paths, ActiveRecord::SchemaMigration).migrate(nil, &filter) }
15
+ elsif Rails::VERSION::STRING >= '5.2'
16
+ -> { ActiveRecord::MigrationContext.new(paths).migrate(nil, &filter) }
15
17
  else
16
- ActiveRecord::Migrator.migrate(paths, &filter)
18
+ -> { ActiveRecord::Migrator.migrate(paths, &filter) }
17
19
  end
18
- }
19
20
  if quiet
20
21
  silence_active_record(&migrate)
21
22
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- VERSION = '0.16.11'
4
+ VERSION = '0.16.12'
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: 0.16.11
4
+ version: 0.16.12
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: 2019-04-27 00:00:00.000000000 Z
12
+ date: 2019-07-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: active_record_union
@@ -130,6 +130,9 @@ dependencies:
130
130
  - - ">="
131
131
  - !ruby/object:Gem::Version
132
132
  version: 4.2.10
133
+ - - "<="
134
+ - !ruby/object:Gem::Version
135
+ version: 6.0.0.rc2
133
136
  type: :runtime
134
137
  prerelease: false
135
138
  version_requirements: !ruby/object:Gem::Requirement
@@ -137,6 +140,9 @@ dependencies:
137
140
  - - ">="
138
141
  - !ruby/object:Gem::Version
139
142
  version: 4.2.10
143
+ - - "<="
144
+ - !ruby/object:Gem::Version
145
+ version: 6.0.0.rc2
140
146
  - !ruby/object:Gem::Dependency
141
147
  name: rb-gravatar
142
148
  requirement: !ruby/object:Gem::Requirement
@@ -751,6 +757,7 @@ files:
751
757
  - app/forms/thredded/user_preferences_form.rb
752
758
  - app/helpers/thredded/application_helper.rb
753
759
  - app/helpers/thredded/icon_helper.rb
760
+ - app/helpers/thredded/moderation_helper.rb
754
761
  - app/helpers/thredded/nav_helper.rb
755
762
  - app/helpers/thredded/render_helper.rb
756
763
  - app/helpers/thredded/urls_helper.rb
@@ -854,6 +861,7 @@ files:
854
861
  - app/views/thredded/moderation/_post.html.erb
855
862
  - app/views/thredded/moderation/_post_moderation_actions.html.erb
856
863
  - app/views/thredded/moderation/_post_moderation_record.html.erb
864
+ - app/views/thredded/moderation/_post_moderation_record_content.html.erb
857
865
  - app/views/thredded/moderation/_user_moderation_state.html.erb
858
866
  - app/views/thredded/moderation/_user_post.html.erb
859
867
  - app/views/thredded/moderation/_users_search_form.html.erb