thredded 0.16.14 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -7
  3. data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -1
  4. data/app/controllers/concerns/thredded/render_preview.rb +6 -0
  5. data/app/controllers/thredded/application_controller.rb +5 -10
  6. data/app/controllers/thredded/topics_controller.rb +0 -6
  7. data/app/helpers/thredded/application_helper.rb +1 -1
  8. data/app/helpers/thredded/icon_helper.rb +1 -1
  9. data/app/helpers/thredded/urls_helper.rb +1 -1
  10. data/app/models/concerns/thredded/post_common.rb +5 -4
  11. data/app/models/concerns/thredded/topic_common.rb +1 -1
  12. data/app/models/concerns/thredded/user_topic_read_state_common.rb +7 -8
  13. data/app/models/thredded/messageboard.rb +5 -4
  14. data/app/models/thredded/notifications_for_followed_topics.rb +1 -1
  15. data/app/models/thredded/post.rb +3 -2
  16. data/app/models/thredded/post_moderation_record.rb +8 -9
  17. data/app/models/thredded/private_post.rb +2 -2
  18. data/app/models/thredded/private_topic.rb +4 -2
  19. data/app/models/thredded/topic.rb +2 -2
  20. data/app/models/thredded/user_detail.rb +1 -1
  21. data/app/models/thredded/user_extender.rb +1 -1
  22. data/app/models/thredded/user_private_topic_read_state.rb +3 -0
  23. data/app/models/thredded/user_topic_read_state.rb +3 -0
  24. data/app/view_models/thredded/post_view.rb +1 -0
  25. data/app/view_models/thredded/posts_page_view.rb +10 -9
  26. data/app/view_models/thredded/private_topics_page_view.rb +10 -6
  27. data/app/view_models/thredded/topic_posts_page_view.rb +16 -3
  28. data/app/view_models/thredded/topics_page_view.rb +9 -6
  29. data/app/views/layouts/thredded/application.html.erb +2 -2
  30. data/app/views/thredded/messageboards/_form.html.erb +3 -3
  31. data/app/views/thredded/posts_common/_actions.html.erb +1 -1
  32. data/app/views/thredded/posts_common/_content.html.erb +1 -1
  33. data/app/views/thredded/posts_common/actions/_quote.html.erb +1 -1
  34. data/app/views/thredded/shared/_page.html.erb +1 -1
  35. data/app/views/thredded/shared/nav/_moderation.html.erb +3 -3
  36. data/app/views/thredded/shared/nav/_notification_preferences.html.erb +1 -1
  37. data/app/views/thredded/shared/nav/_private_topics.html.erb +3 -3
  38. data/app/views/thredded/shared/nav/_standalone.html.erb +2 -2
  39. data/app/views/thredded/topics/_header.html.erb +1 -1
  40. data/bin/create_migration_fixture +23 -0
  41. data/config/i18n-tasks.yml +5 -0
  42. data/config/locales/de.yml +2 -0
  43. data/config/locales/en.yml +3 -1
  44. data/config/locales/es.yml +2 -0
  45. data/config/locales/fr.yml +2 -0
  46. data/config/locales/it.yml +4 -2
  47. data/config/locales/pl.yml +2 -0
  48. data/config/locales/pt-BR.yml +2 -0
  49. data/config/locales/ru.yml +2 -0
  50. data/config/locales/zh-CN.yml +2 -0
  51. data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +1 -1
  52. data/lib/generators/thredded/install/templates/initializer.rb +2 -2
  53. data/lib/thredded/arel_compat.rb +0 -32
  54. data/lib/thredded/base_migration.rb +1 -1
  55. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +91 -28
  56. data/lib/thredded/compat.rb +35 -0
  57. data/lib/thredded/database_seeder.rb +11 -4
  58. data/lib/thredded/db_tools.rb +2 -4
  59. data/lib/thredded/email_transformer.rb +2 -2
  60. data/lib/thredded/html_pipeline/onebox_filter.rb +1 -4
  61. data/lib/thredded/version.rb +1 -1
  62. data/lib/thredded.rb +3 -23
  63. metadata +43 -31
  64. data/bin/rubocop +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b96a82c0482d773cd074fdd77a4e67a408ba43e2e079bbbd2a4eefeb55085f37
4
- data.tar.gz: 6949b12a03a39430340dbd8870cb6e5cc4dcddc583e093de576e2a3621becdb4
3
+ metadata.gz: 8a7da465ff23d46ffb17f30aef22bc8195dc7551f485d28d698f3a8e20aaf958
4
+ data.tar.gz: 24d752703abdc40ba28ffa48d931d70d5c9baeff461467059e7f2c2fbdfea27c
5
5
  SHA512:
6
- metadata.gz: 4403418748bbb9b7bdcf5a503908fb526d60ff0f6100d11be98d64f625da8e9479c2be127adb2b88435686371d58cb75c39d1e4c73789e5e8297eabf9d01198d
7
- data.tar.gz: 154ddbcd92f7e8f705bf4fe27bbc32f03fff0e3af7fdb81aca82143783dfd389eed1cbd516802356288af0c839cd3ddc61087209037c60bc521bee072939efa6
6
+ metadata.gz: 6dec36328aba9edd1143d2aecaa70970ba48897ed470e97785c534067c6d5f38e0e175d4637a5824f1d09827059ab508147c2c721400a8027e966884f235a74d
7
+ data.tar.gz: c35c5f19f198b78b0d6426b96e1f15863cb29ff74c5a130d665a1fde1dd89d9f5af8bb194b0991d2a7bf965aa9fe6309af0de03246a6bf7d2711e1356b34d636
data/README.md CHANGED
@@ -96,7 +96,7 @@ Then, see the rest of this Readme for more information about using and customizi
96
96
  Add the gem to your Gemfile:
97
97
 
98
98
  ```ruby
99
- gem 'thredded', '~> 0.16.14'
99
+ gem 'thredded', '~> 1.0'
100
100
  ```
101
101
 
102
102
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -174,7 +174,7 @@ mkdir -p app/views/thredded/shared/nav && cp "$(bundle show thredded)/$_/_standa
174
174
 
175
175
  ### Application layout
176
176
 
177
- You can also use Thredded with your application (or other) layout by by setting `Thredded.layout` in the initializer.
177
+ You can also use Thredded with your application (or other) layout by setting `Thredded.layout` in the initializer.
178
178
 
179
179
  In this case, you will need to reference your paths/routes carefully and pull in thredded assets (styles and javascript):
180
180
 
@@ -655,12 +655,24 @@ By default, the dummy app server uses Webpack for JavaScript.
655
655
  To use Sprockets instead, run:
656
656
 
657
657
  ```bash
658
- THREDDED_TESTAPP_WEBPACK=1 bin/rails s
658
+ THREDDED_TESTAPP_SPROCKETS=1 bin/rails s
659
659
  ```
660
660
 
661
+ alternatively you can use guard (which comes with activereload to make development more pleasant) with:
662
+
663
+ export THREDDED_USE_GUARD=1
664
+ bundle
665
+ bundle exec guard
666
+
667
+
661
668
  ### Testing
662
669
 
663
- To run the tests, just run `rspec`. The test suite will re-create the test database on every run, so there is no need to
670
+ In order to run the tests locally, you will need to be running webpack-dev-server (or do a manual compilation):
671
+
672
+ cd spec/dummy && yarn && cd -
673
+ BUNDLE_GEMFILE="${PWD}/Gemfile" spec/dummy/bin/webpack-dev-server
674
+
675
+ Then to run the tests, just run `rspec`. The test suite will re-create the test database on every run, so there is no need to
664
676
  run tasks that maintain the test database.
665
677
 
666
678
  By default, SQLite is used in development and test. On Travis, the tests will run using SQLite, PostgreSQL, MySQL,
@@ -677,8 +689,8 @@ sudo apt-get install chromium-chromedriver
677
689
  On Mac, run:
678
690
 
679
691
  ```bash
680
- brew cask install chromium
681
- brew cask install chromedriver
692
+ brew install --cask chromium
693
+ brew install --cask chromedriver
682
694
  ```
683
695
 
684
696
  To get better page saves (`page.save_and_open_page`) from local capybara specs ensure you are running the server locally
@@ -777,7 +789,7 @@ start the included docker-compose.yml file with:
777
789
 
778
790
  ```console
779
791
  docker-compose build
780
- docker-compose up -d
792
+ docker-compose up
781
793
  ```
782
794
 
783
795
  The above will build and run everything, daemonized, resulting in a running
@@ -27,7 +27,9 @@
27
27
  }, 200, false);
28
28
 
29
29
  textarea.addEventListener('input', onChange, false);
30
-
30
+ if(textarea.value.trim() !== '') {
31
+ onChange();
32
+ }
31
33
  this.requestId = 0;
32
34
  }
33
35
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  module Thredded
4
4
  module RenderPreview
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ skip_forgery_protection
9
+ end
10
+
5
11
  protected
6
12
 
7
13
  def render_preview
@@ -4,7 +4,11 @@ module Thredded
4
4
  class ApplicationController < ::ApplicationController # rubocop:disable Metrics/ClassLength
5
5
  layout :thredded_layout
6
6
  include ::Thredded::UrlsHelper
7
- include Pundit
7
+ if defined?(Pundit::Authorization)
8
+ include Pundit::Authorization
9
+ else
10
+ include Pundit
11
+ end
8
12
 
9
13
  helper Thredded::Engine.helpers
10
14
  helper_method \
@@ -63,15 +67,6 @@ module Thredded
63
67
  @is_thredded_moderator = !thredded_current_user.thredded_can_moderate_messageboards.empty?
64
68
  end
65
69
 
66
- if Rails::VERSION::MAJOR < 5
67
- # redirect_back polyfill
68
- def redirect_back(fallback_location:, **args)
69
- redirect_to :back, args
70
- rescue ActionController::RedirectBackError
71
- redirect_to fallback_location, args
72
- end
73
- end
74
-
75
70
  # @param given [Hash]
76
71
  # @return [Boolean] whether the given params are a subset of the controller's {#params}.
77
72
  def params_match?(given = {})
@@ -203,12 +203,6 @@ module Thredded
203
203
  @messageboard = topic.messageboard
204
204
  end
205
205
 
206
- def topic_params
207
- params
208
- .require(:topic)
209
- .permit(:title, :locked, :sticky, category_ids: [])
210
- end
211
-
212
206
  def topic_params_for_update
213
207
  params
214
208
  .require(:topic)
@@ -92,7 +92,7 @@ module Thredded
92
92
  end
93
93
 
94
94
  def paginate(collection, args = {})
95
- super(collection, args.reverse_merge(views_prefix: 'thredded'))
95
+ super(collection, **args.reverse_merge(views_prefix: 'thredded'))
96
96
  end
97
97
 
98
98
  # @param topic [BaseTopicView]
@@ -25,7 +25,7 @@ module Thredded
25
25
  def inline_svg_once(filename, id:, **transform_params)
26
26
  return if @already_inlined_svg_ids&.include?(id)
27
27
  record_already_inlined_svg(filename, id)
28
- inline_svg(filename, id: id, **transform_params)
28
+ inline_svg_tag(filename, id: id, **transform_params)
29
29
  end
30
30
 
31
31
  private
@@ -54,7 +54,7 @@ module Thredded
54
54
  # @param user [Thredded.user_class] the current user
55
55
  # @return [String] path to the topic page with the post anchor.
56
56
  def post_path(post, user:, **params)
57
- post_url(post, params.merge(user: user, only_path: true))
57
+ post_url(post, **params.merge(user: user, only_path: true))
58
58
  end
59
59
 
60
60
  # @param post [Post, PrivatePost]
@@ -23,12 +23,12 @@ module Thredded
23
23
  result = all
24
24
  owners_by_id = result.each_with_object({}) { |r, h| h[r.postable_id] = r.postable }
25
25
  next result if owners_by_id.empty?
26
- preloader = ActiveRecord::Associations::Preloader.new.preload(
27
- owners_by_id.values, :first_post,
28
- unscoped.where(<<~SQL.delete("\n"))
26
+ preloader = Thredded::Compat.association_preloader(
27
+ records: owners_by_id.values, associations: [:first_post],
28
+ scope: unscoped.where(<<~SQL.delete("\n"))
29
29
  #{posts_table_name}.created_at = (
30
30
  SELECT MAX(p2.created_at) from #{posts_table_name} p2 WHERE p2.postable_id = #{posts_table_name}.postable_id)
31
- SQL
31
+ SQL
32
32
  )
33
33
  preloader[0].preloaded_records.each do |post|
34
34
  topic = owners_by_id.delete(post.postable_id)
@@ -81,6 +81,7 @@ module Thredded
81
81
  protected
82
82
 
83
83
  def update_unread_posts_count
84
+ return if destroyed_by_association
84
85
  postable.user_read_states.update_post_counts!
85
86
  end
86
87
 
@@ -9,7 +9,7 @@ module Thredded
9
9
  belongs_to :last_user, # rubocop:disable Rails/InverseOf
10
10
  class_name: Thredded.user_class_name,
11
11
  foreign_key: 'last_user_id',
12
- **(Thredded.rails_gte_51? ? { optional: true } : {})
12
+ optional: true
13
13
 
14
14
  scope :order_recently_posted_first, -> { order(last_post_at: :desc, id: :desc) }
15
15
  scope :on_page, ->(page_num) { page(page_num) }
@@ -27,10 +27,9 @@ module Thredded
27
27
  end
28
28
 
29
29
  def calculate_post_counts
30
+ relation = self.class.visible_posts_scope(user).where(postable_id: postable_id)
30
31
  unread_posts_count, read_posts_count =
31
- self.class.visible_posts_scope(user)
32
- .where(postable_id: postable_id)
33
- .pluck(*self.class.post_counts_arel(read_at))[0]
32
+ relation.pluck(*self.class.post_counts_arel(read_at))[0]
34
33
  { unread_posts_count: unread_posts_count || 0, read_posts_count: read_posts_count || 0 }
35
34
  end
36
35
 
@@ -45,7 +44,7 @@ module Thredded
45
44
  selects << states[Arel.star] if !is_a?(ActiveRecord::Relation) || select_values.empty?
46
45
  selects += [
47
46
  Arel::Nodes::Case.new(states[:unread_posts_count].not_eq(0))
48
- .when(Thredded::ArelCompat.true_value(self)).then(
47
+ .when(true).then(
49
48
  Arel::Nodes::Addition.new(
50
49
  Thredded::ArelCompat.integer_division(self, states[:read_posts_count], posts_per_page), 1
51
50
  )
@@ -76,11 +75,11 @@ module Thredded
76
75
  [
77
76
  Arel::Nodes::Sum.new(
78
77
  [Arel::Nodes::Case.new(posts[:created_at].gt(read_at))
79
- .when(Thredded::ArelCompat.true_value(self)).then(1).else(0)]
78
+ .when(true).then(1).else(0)]
80
79
  ).as('unread_posts_count'),
81
80
  Arel::Nodes::Sum.new(
82
81
  [Arel::Nodes::Case.new(posts[:created_at].gt(read_at))
83
- .when(Thredded::ArelCompat.true_value(self)).then(0).else(1)]
82
+ .when(true).then(0).else(1)]
84
83
  ).as('read_posts_count')
85
84
  ]
86
85
  end
@@ -89,9 +88,9 @@ module Thredded
89
88
  def calculate_post_counts
90
89
  states = arel_table
91
90
  posts = post_class.arel_table
92
- joins(states.join(posts).on(states[:postable_id].eq(posts[:postable_id])).join_sources)
91
+ relation = joins(states.join(posts).on(states[:postable_id].eq(posts[:postable_id])).join_sources)
93
92
  .group(states[:id])
94
- .pluck(states[:id], *post_counts_arel(states[:read_at], posts: posts))
93
+ relation.pluck(states[:id], *post_counts_arel(states[:read_at], posts: posts))
95
94
  end
96
95
  end
97
96
  end
@@ -39,7 +39,7 @@ module Thredded
39
39
  has_many :posts, dependent: :destroy, inverse_of: :messageboard
40
40
  has_many :topics, dependent: :destroy, inverse_of: :messageboard
41
41
 
42
- belongs_to :last_topic, class_name: 'Thredded::Topic', **(Thredded.rails_gte_51? ? { optional: true } : {})
42
+ belongs_to :last_topic, class_name: 'Thredded::Topic', optional: true
43
43
 
44
44
  has_many :user_details, through: :posts
45
45
  has_many :messageboard_users,
@@ -65,7 +65,7 @@ module Thredded
65
65
  inverse_of: :messageboards,
66
66
  foreign_key: :messageboard_group_id,
67
67
  class_name: 'Thredded::MessageboardGroup',
68
- **(Thredded.rails_gte_51? ? { optional: true } : {})
68
+ optional: true
69
69
 
70
70
  has_many :post_moderation_records, inverse_of: :messageboard, dependent: :delete_all
71
71
  scope :top_level_messageboards, -> { where(group: nil) }
@@ -139,9 +139,10 @@ module Thredded
139
139
  .and(read_states[:user_id].eq(user.id))
140
140
  .and(read_states[:unread_posts_count].eq(0))
141
141
 
142
- joins(:topics).merge(topics_scope).joins(
142
+ relation = joins(:topics).merge(topics_scope).joins(
143
143
  messageboards.outer_join(read_states).on(read_states_join_cond).join_sources
144
- ).group(messageboards[:id]).pluck(
144
+ ).group(messageboards[:id])
145
+ relation.pluck(
145
146
  :id,
146
147
  Arel::Nodes::Subtraction.new(topics[:id].count, read_states[:id].count)
147
148
  ).to_h
@@ -7,7 +7,7 @@ module Thredded
7
7
  inverse_of: :thredded_notifications_for_followed_topics
8
8
  belongs_to :messageboard,
9
9
  # If messageboard is `nil`, these are the global preferences.
10
- **(Thredded.rails_gte_51? ? { optional: true } : {})
10
+ optional: true
11
11
  belongs_to :user_preference,
12
12
  primary_key: :user_id,
13
13
  foreign_key: :user_id,
@@ -8,7 +8,7 @@ module Thredded
8
8
  belongs_to :user,
9
9
  class_name: Thredded.user_class_name,
10
10
  inverse_of: :thredded_posts,
11
- **(Thredded.rails_gte_51? ? { optional: true } : {})
11
+ optional: true
12
12
  belongs_to :messageboard,
13
13
  counter_cache: true,
14
14
  inverse_of: :posts
@@ -21,7 +21,7 @@ module Thredded
21
21
  primary_key: :user_id,
22
22
  foreign_key: :user_id,
23
23
  counter_cache: true,
24
- **(Thredded.rails_gte_51? ? { optional: true } : {})
24
+ optional: true
25
25
  has_many :moderation_records,
26
26
  class_name: 'Thredded::PostModerationRecord',
27
27
  dependent: :nullify
@@ -78,6 +78,7 @@ module Thredded
78
78
  end
79
79
 
80
80
  def update_parent_last_user_and_time_from_last_post
81
+ return if destroyed_by_association
81
82
  postable.update_last_user_and_time_from_last_post!
82
83
  messageboard.update_last_topic!
83
84
  end
@@ -3,25 +3,24 @@
3
3
  module Thredded
4
4
  class PostModerationRecord < ActiveRecord::Base
5
5
  include Thredded::ModerationState
6
- # Rails 4 doesn't support enum _prefix
7
- enum previous_moderation_state: moderation_states, _prefix: :previous if Rails::VERSION::MAJOR >= 5
6
+ enum previous_moderation_state: moderation_states, _prefix: :previous
8
7
  validates :previous_moderation_state, presence: true
9
8
 
10
9
  scope :order_newest_first, -> { order(created_at: :desc, id: :desc) }
11
10
 
12
11
  belongs_to :messageboard, inverse_of: :post_moderation_records
13
- validates :messageboard_id, presence: true unless Thredded.rails_gte_51?
12
+
14
13
  belongs_to :post,
15
14
  inverse_of: :moderation_records,
16
- **(Thredded.rails_gte_51? ? { optional: true } : {})
15
+ optional: true
17
16
  belongs_to :post_user,
18
17
  class_name: Thredded.user_class_name,
19
18
  inverse_of: :thredded_post_moderation_records,
20
- **(Thredded.rails_gte_51? ? { optional: true } : {})
19
+ optional: true
21
20
  belongs_to :moderator,
22
21
  class_name: Thredded.user_class_name,
23
22
  inverse_of: :thredded_post_moderation_records,
24
- **(Thredded.rails_gte_51? ? { optional: true } : {})
23
+ optional: true
25
24
 
26
25
  validates_each :moderation_state do |record, attr, value|
27
26
  record.errors.add attr, "Post moderation_state is already #{value}" if record.previous_moderation_state == value
@@ -30,7 +29,9 @@ module Thredded
30
29
  scope :preload_first_topic_post, -> {
31
30
  posts_table_name = Thredded::Post.quoted_table_name
32
31
  result = all
33
- owners_by_id = result.each_with_object({}) { |r, h| h[r.post.postable_id] = r.post.postable }
32
+ owners_by_id = result.each_with_object({}) do |r, h|
33
+ h[r.post.postable_id] = r.post.postable if r.post
34
+ end
34
35
  next result if owners_by_id.empty?
35
36
  preloader = ActiveRecord::Associations::Preloader.new.preload(
36
37
  owners_by_id.values, :first_post,
@@ -60,8 +61,6 @@ module Thredded
60
61
  # @param [Symbol, String] moderation_state
61
62
  # @return [Thredded::PostModerationRecord] the newly created persisted record
62
63
  def self.record!(moderator:, post:, previous_moderation_state:, moderation_state:)
63
- # Rails 4 doesn't support enum _prefix
64
- previous_moderation_state = moderation_states[previous_moderation_state.to_s] if Rails::VERSION::MAJOR < 5
65
64
  create!(
66
65
  previous_moderation_state: previous_moderation_state,
67
66
  moderation_state: moderation_state,
@@ -7,7 +7,7 @@ module Thredded
7
7
  belongs_to :user,
8
8
  class_name: Thredded.user_class_name,
9
9
  inverse_of: :thredded_private_posts,
10
- **(Thredded.rails_gte_51? ? { optional: true } : {})
10
+ optional: true
11
11
  belongs_to :postable,
12
12
  class_name: 'Thredded::PrivateTopic',
13
13
  inverse_of: :posts,
@@ -16,7 +16,7 @@ module Thredded
16
16
  inverse_of: :private_posts,
17
17
  primary_key: :user_id,
18
18
  foreign_key: :user_id,
19
- **(Thredded.rails_gte_51? ? { optional: true } : {})
19
+ optional: true
20
20
 
21
21
  after_commit :update_parent_last_user_and_timestamp, on: %i[create destroy]
22
22
  after_commit :notify_users, on: [:create]
@@ -15,12 +15,12 @@ module Thredded
15
15
  belongs_to :user,
16
16
  class_name: Thredded.user_class_name,
17
17
  inverse_of: :thredded_private_topics,
18
- **(Thredded.rails_gte_51? ? { optional: true } : {})
18
+ optional: true
19
19
  belongs_to :user_detail,
20
20
  primary_key: :user_id,
21
21
  foreign_key: :user_id,
22
22
  inverse_of: :private_topics,
23
- **(Thredded.rails_gte_51? ? { optional: true } : {})
23
+ optional: true
24
24
 
25
25
  has_many :posts,
26
26
  class_name: 'Thredded::PrivatePost',
@@ -102,6 +102,8 @@ module Thredded
102
102
  end
103
103
 
104
104
  def ensure_user_in_private_users
105
+ # TODO: investigate performance of this. Seems to take a long time and be repeatedly called in tests
106
+ # can we avoid callling this so often
105
107
  users << user if user.present? && !users.include?(user)
106
108
  end
107
109
 
@@ -37,7 +37,7 @@ module Thredded
37
37
  belongs_to :user,
38
38
  class_name: Thredded.user_class_name,
39
39
  inverse_of: :thredded_topics,
40
- **(Thredded.rails_gte_51? ? { optional: true } : {})
40
+ optional: true
41
41
 
42
42
  belongs_to :messageboard,
43
43
  counter_cache: true,
@@ -50,7 +50,7 @@ module Thredded
50
50
  foreign_key: :user_id,
51
51
  inverse_of: :topics,
52
52
  counter_cache: :topics_count,
53
- **(Thredded.rails_gte_51? ? { optional: true } : {})
53
+ optional: true
54
54
 
55
55
  has_many :posts,
56
56
  autosave: true,
@@ -7,7 +7,7 @@ module Thredded
7
7
  belongs_to :user, class_name: Thredded.user_class_name, inverse_of: :thredded_user_detail
8
8
  validates :user_id,
9
9
  uniqueness: { case_sensitive: true },
10
- **(Thredded.rails_gte_51? ? {} : { presence: true })
10
+ presence: true
11
11
 
12
12
  with_options foreign_key: :user_id, primary_key: :user_id, inverse_of: :user_detail, dependent: :nullify do
13
13
  has_many :topics, class_name: 'Thredded::Topic'
@@ -30,7 +30,7 @@ module Thredded
30
30
  class_name: 'Thredded::MessageboardNotificationsForFollowedTopics'
31
31
  has_many :thredded_notifications_for_private_topics, class_name: 'Thredded::NotificationsForPrivateTopics'
32
32
  has_many :thredded_post_notifications, class_name: 'Thredded::UserPostNotification'
33
- has_many :thredded_private_users, class_name: 'Thredded::PrivateUser'
33
+ has_many :thredded_private_users, class_name: 'Thredded::PrivateUser', inverse_of: :user
34
34
  has_many :thredded_topic_read_states, class_name: 'Thredded::UserTopicReadState'
35
35
  has_many :thredded_private_topic_read_states, class_name: 'Thredded::UserPrivateTopicReadState'
36
36
  has_many :thredded_topic_follows, class_name: 'Thredded::UserTopicFollow'
@@ -27,6 +27,9 @@ module Thredded
27
27
  return if !overwrite_newer && state.read_at? && state.read_at >= post.created_at
28
28
  state.read_at = post.created_at
29
29
  state.update!(state.calculate_post_counts)
30
+ rescue ActiveRecord::RecordNotUnique
31
+ # The record has been created from another connection, retry to find it.
32
+ retry
30
33
  end
31
34
 
32
35
  # @param [Thredded.user_class] user
@@ -31,6 +31,9 @@ module Thredded
31
31
  state.messageboard_id = post.messageboard_id
32
32
  state.read_at = post.created_at
33
33
  state.update!(state.calculate_post_counts)
34
+ rescue ActiveRecord::RecordNotUnique
35
+ # The record has been created from another connection, retry to find it.
36
+ retry
34
37
  end
35
38
 
36
39
  # @param [Thredded.user_class] user
@@ -13,6 +13,7 @@ module Thredded
13
13
  :blocked?,
14
14
  :last_moderation_record,
15
15
  :cache_key,
16
+ :cache_version,
16
17
  :cache_key_with_version,
17
18
  to: :@post
18
19
 
@@ -3,24 +3,25 @@
3
3
  module Thredded
4
4
  # A view model for a page of PostViews.
5
5
  class PostsPageView
6
- delegate :to_a,
6
+ delegate :each,
7
+ :each_with_index,
8
+ :map,
9
+ :size,
10
+ :to_a,
7
11
  :to_ary,
8
12
  :present?,
9
13
  to: :@post_views
10
14
  delegate :total_pages,
11
15
  :current_page,
12
16
  :limit_value,
13
- to: :@paginated_scope
14
-
15
- # @return [Thredded::BaseTopicView]
16
- attr_reader :topic
17
+ to: :@posts_paginator
17
18
 
18
19
  # @param user [Thredded.user_class] the user who is viewing the posts page
19
- # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
20
- def initialize(user, paginated_scope, topic_view: nil)
21
- @paginated_scope = paginated_scope
20
+ # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>] a kaminari-decorated ".page" scope
21
+ def initialize(user, paginated_scope)
22
+ @posts_paginator = paginated_scope
22
23
  @post_views = paginated_scope.map do |post|
23
- Thredded::PostView.new(post, Pundit.policy!(user, post), topic_view: topic_view)
24
+ Thredded::PostView.new(post, Pundit.policy!(user, post))
24
25
  end
25
26
  end
26
27
  end
@@ -3,7 +3,11 @@
3
3
  module Thredded
4
4
  # A view model for a page of BaseTopicViews.
5
5
  class PrivateTopicsPageView
6
- delegate :to_a,
6
+ delegate :each,
7
+ :each_with_index,
8
+ :map,
9
+ :size,
10
+ :to_a,
7
11
  :to_ary,
8
12
  :blank?,
9
13
  :empty?,
@@ -11,13 +15,13 @@ module Thredded
11
15
  delegate :total_pages,
12
16
  :current_page,
13
17
  :limit_value,
14
- to: :@topics_page_scope
18
+ to: :@topics_paginator
15
19
 
16
- # @param user [Thredded.user_class] the user who is viewing the posts page
17
- # @param topics_page_scope [ActiveRecord::Relation<Thredded::Topic>]
20
+ # @param user [Thredded.user_class] the user who is viewing the private topics page
21
+ # @param topics_page_scope [ActiveRecord::Relation<Thredded::PrivateTopic>] a kaminari-decorated ".page" scope
18
22
  def initialize(user, topics_page_scope)
19
- @topics_page_scope = refine_scope(topics_page_scope)
20
- @topic_views = @topics_page_scope.with_read_states(user).map do |(topic, read_state)|
23
+ @topics_paginator = refine_scope(topics_page_scope)
24
+ @topic_views = @topics_paginator.with_read_states(user).map do |(topic, read_state)|
21
25
  Thredded::PrivateTopicView.new(topic, read_state, Pundit.policy!(user, topic))
22
26
  end
23
27
  end
@@ -2,15 +2,28 @@
2
2
 
3
3
  module Thredded
4
4
  # A view model for a page of PostViews of a Topic.
5
- class TopicPostsPageView < Thredded::PostsPageView
5
+ class TopicPostsPageView
6
+ delegate :each,
7
+ :each_with_index,
8
+ :map,
9
+ :size,
10
+ :to_a,
11
+ :to_ary,
12
+ :present?,
13
+ to: :@post_views
14
+ delegate :total_pages,
15
+ :current_page,
16
+ :limit_value,
17
+ to: :@posts_paginator
18
+
6
19
  # @return [Thredded::BaseTopicView]
7
20
  attr_reader :topic
8
21
 
9
22
  # @param user [Thredded.user_class] the user who is viewing the posts page
10
23
  # @param topic [Thredded::TopicCommon]
11
- # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
24
+ # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>] a kaminari-decorated ".page" scope
12
25
  def initialize(user, topic, paginated_scope)
13
- @paginated_scope = paginated_scope
26
+ @posts_paginator = paginated_scope
14
27
  @topic = "#{paginated_scope.reflect_on_association(:postable).klass}View".constantize.from_user(topic, user)
15
28
  prev_read = false
16
29
  @post_views = paginated_scope.map.with_index do |post, i|
@@ -8,18 +8,21 @@ module Thredded
8
8
  :blank?,
9
9
  :empty?,
10
10
  :[],
11
+ :each,
12
+ :map,
13
+ :size,
11
14
  to: :@topic_views
12
15
  delegate :total_pages,
13
16
  :current_page,
14
17
  :limit_value,
15
- to: :@topics_page_scope
18
+ to: :@topics_paginator
16
19
 
17
- # @param user [Thredded.user_class] the user who is viewing the posts page
18
- # @param topics_page_scope [ActiveRecord::Relation<Thredded::Topic>]
19
- # @param topic_view_class [Class] view_model for topic instances
20
+ # @param user [Thredded.user_class] the user who is viewing the topics page
21
+ # @param topics_page_scope [ActiveRecord::Relation<Thredded::Topic>] a kaminari-decorated ".page" scope
22
+ # @param topic_view_class [Class<TopicView>] view_model for topic instances
20
23
  def initialize(user, topics_page_scope, topic_view_class: TopicView)
21
- @topics_page_scope = refine_scope(topics_page_scope)
22
- @topic_views = @topics_page_scope.with_read_and_follow_states(user).map do |(topic, read_state, follow)|
24
+ @topics_paginator = refine_scope(topics_page_scope)
25
+ @topic_views = @topics_paginator.with_read_and_follow_states(user).map do |(topic, read_state, follow)|
23
26
  topic_view_class.new(topic, read_state, follow, Pundit.policy!(user, topic))
24
27
  end
25
28
  end
@@ -5,12 +5,12 @@
5
5
  <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
6
6
  <%= stylesheet_link_tag 'thredded', 'data-turbolinks-track': 'reload' %>
7
7
  <%= csrf_meta_tag %>
8
- <%= csp_meta_tag if Thredded.rails_supports_csp_nonce? %>
8
+ <%= csp_meta_tag %>
9
9
  <%= javascript_include_tag 'thredded',
10
10
  async: !Rails.application.config.assets.debug,
11
11
  defer: true,
12
12
  'data-turbolinks-track': 'reload' %>
13
- <%== Gravatar.prefetch_dns %>
13
+ <%= RailsGravatar.prefetch_dns_tag %>
14
14
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
15
15
  </head>
16
16
  <body>