thredded 0.16.11 → 0.16.12

Sign up to get free protection for your applications and to get access to all the features.
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