thredded 0.8.4 → 0.9.1

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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -9
  3. data/app/assets/images/thredded/breadcrumb-chevron.svg +1 -1
  4. data/app/assets/images/thredded/follow.svg +1 -1
  5. data/app/assets/images/thredded/moderation.svg +1 -4
  6. data/app/assets/images/thredded/private-messages.svg +1 -4
  7. data/app/assets/images/thredded/settings.svg +1 -4
  8. data/app/assets/images/thredded/unfollow.svg +1 -1
  9. data/app/assets/javascripts/thredded/components/time_stamps.es6 +1 -1
  10. data/app/assets/javascripts/thredded/components/user_preferences_form.es6 +26 -49
  11. data/app/assets/javascripts/thredded/dependencies.js +2 -1
  12. data/app/assets/stylesheets/thredded/base/_grid.scss +4 -4
  13. data/app/assets/stylesheets/thredded/base/_typography.scss +18 -0
  14. data/app/assets/stylesheets/thredded/components/_base.scss +4 -0
  15. data/app/assets/stylesheets/thredded/components/_currently-online.scss +4 -0
  16. data/app/assets/stylesheets/thredded/components/_form-list.scss +5 -0
  17. data/app/assets/stylesheets/thredded/components/_messageboard.scss +3 -0
  18. data/app/assets/stylesheets/thredded/components/_post-form.scss +7 -4
  19. data/app/assets/stylesheets/thredded/components/_post.scss +3 -0
  20. data/app/assets/stylesheets/thredded/components/_topic-delete.scss +3 -0
  21. data/app/assets/stylesheets/thredded/components/_topic-header.scss +20 -0
  22. data/app/assets/stylesheets/thredded/components/_topics.scss +13 -1
  23. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +1 -1
  24. data/app/assets/stylesheets/thredded/layout/_navigation.scss +3 -1
  25. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +4 -0
  26. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +3 -0
  27. data/app/commands/thredded/at_notification_extractor.rb +1 -2
  28. data/app/commands/thredded/autofollow_mentioned_users.rb +0 -1
  29. data/app/commands/thredded/notify_following_users.rb +20 -11
  30. data/app/commands/thredded/notify_private_topic_users.rb +12 -28
  31. data/app/controllers/thredded/application_controller.rb +8 -4
  32. data/app/controllers/thredded/messageboards_controller.rb +2 -2
  33. data/app/controllers/thredded/moderation_controller.rb +12 -13
  34. data/app/controllers/thredded/posts_controller.rb +1 -1
  35. data/app/controllers/thredded/preferences_controller.rb +11 -7
  36. data/app/controllers/thredded/private_topics_controller.rb +13 -9
  37. data/app/controllers/thredded/topics_controller.rb +20 -12
  38. data/app/forms/thredded/user_preferences_form.rb +23 -4
  39. data/app/helpers/thredded/application_helper.rb +11 -1
  40. data/app/mailers/thredded/post_mailer.rb +2 -3
  41. data/app/mailers/thredded/private_topic_mailer.rb +2 -3
  42. data/app/models/concerns/thredded/content_moderation_state.rb +1 -1
  43. data/app/models/concerns/thredded/notifier_preference.rb +18 -0
  44. data/app/models/concerns/thredded/post_common.rb +5 -1
  45. data/{lib → app/models/concerns}/thredded/search_parser.rb +0 -0
  46. data/app/models/concerns/thredded/topic_common.rb +3 -3
  47. data/{lib → app/models/concerns}/thredded/topics_search.rb +2 -3
  48. data/app/models/thredded/messageboard.rb +11 -0
  49. data/app/models/thredded/messageboard_notifications_for_followed_topics.rb +29 -0
  50. data/app/models/thredded/notifications_for_followed_topics.rb +22 -0
  51. data/app/models/thredded/notifications_for_private_topics.rb +21 -0
  52. data/app/models/thredded/post.rb +5 -3
  53. data/app/models/thredded/post_moderation_record.rb +1 -1
  54. data/app/models/thredded/private_post.rb +1 -1
  55. data/app/models/thredded/private_topic.rb +8 -4
  56. data/app/models/thredded/stats.rb +1 -1
  57. data/app/models/thredded/topic.rb +10 -7
  58. data/app/models/thredded/user_detail.rb +1 -1
  59. data/app/models/thredded/user_extender.rb +3 -1
  60. data/app/models/thredded/user_preference.rb +16 -5
  61. data/app/models/thredded/user_private_topic_read_state.rb +1 -1
  62. data/app/models/thredded/user_topic_read_state.rb +1 -1
  63. data/app/notifiers/thredded/base_notifier.rb +28 -0
  64. data/app/notifiers/thredded/email_notifier.rb +34 -0
  65. data/app/policies/thredded/post_policy.rb +3 -4
  66. data/app/policies/thredded/private_post_policy.rb +2 -3
  67. data/app/policies/thredded/topic_policy.rb +1 -2
  68. data/app/view_hooks/thredded/all_view_hooks.rb +0 -3
  69. data/app/view_models/thredded/base_topic_view.rb +0 -1
  70. data/app/view_models/thredded/post_view.rb +0 -1
  71. data/app/view_models/thredded/posts_page_view.rb +1 -4
  72. data/app/view_models/thredded/private_topic_view.rb +3 -1
  73. data/app/view_models/thredded/private_topics_page_view.rb +8 -3
  74. data/app/view_models/thredded/topic_posts_page_view.rb +1 -2
  75. data/app/view_models/thredded/topic_view.rb +3 -3
  76. data/app/view_models/thredded/topics_page_view.rb +0 -1
  77. data/app/views/thredded/messageboard_groups/new.html.erb +5 -1
  78. data/app/views/thredded/messageboards/_form.html.erb +7 -2
  79. data/app/views/thredded/posts/_form.html.erb +2 -1
  80. data/app/views/thredded/posts/edit.html.erb +3 -2
  81. data/app/views/thredded/posts_common/_form.html.erb +6 -2
  82. data/app/views/thredded/preferences/_form.html.erb +41 -29
  83. data/app/views/thredded/private_posts/_form.html.erb +1 -0
  84. data/app/views/thredded/private_topics/_form.html.erb +3 -2
  85. data/app/views/thredded/private_topics/_header.html.erb +4 -0
  86. data/app/views/thredded/private_topics/_no_private_topics.html.erb +1 -1
  87. data/app/views/thredded/private_topics/_private_topic.html.erb +5 -2
  88. data/app/views/thredded/private_topics/edit.html.erb +2 -1
  89. data/app/views/thredded/private_topics/header/_participant.html.erb +1 -0
  90. data/app/views/thredded/private_topics/index.html.erb +6 -5
  91. data/app/views/thredded/private_topics/new.html.erb +0 -1
  92. data/app/views/thredded/private_topics/private_topic/_participant.html.erb +1 -0
  93. data/app/views/thredded/private_topics/show.html.erb +1 -3
  94. data/app/views/thredded/topics/_form.html.erb +1 -1
  95. data/app/views/thredded/topics/show.html.erb +4 -5
  96. data/config/locales/en.yml +22 -9
  97. data/config/locales/pt-BR.yml +22 -10
  98. data/config/routes.rb +1 -1
  99. data/db/migrate/20160329231848_create_thredded.rb +23 -3
  100. data/db/upgrade_migrations/20160410111522_upgrade_v0_2_to_v0_3.rb +1 -1
  101. data/db/upgrade_migrations/20161113161801_upgrade_v0_8_to_v0_9.rb +56 -0
  102. data/lib/generators/thredded/install/templates/initializer.rb +28 -0
  103. data/lib/thredded.rb +32 -8
  104. data/lib/thredded/content_formatter.rb +3 -2
  105. data/lib/thredded/database_seeder.rb +11 -8
  106. data/lib/thredded/db_tools.rb +82 -0
  107. data/lib/thredded/html_pipeline/wrap_iframes_filter.rb +12 -0
  108. data/lib/thredded/version.rb +1 -1
  109. metadata +18 -8
  110. data/app/models/thredded/null_preference.rb +0 -16
@@ -9,12 +9,12 @@ module Thredded
9
9
  validate :validate_children
10
10
 
11
11
  delegate :follow_topics_on_mention, :follow_topics_on_mention=,
12
- :notify_on_message, :notify_on_message=,
13
- :followed_topic_emails, :followed_topic_emails=,
12
+ :messageboard_notifications_for_followed_topics_attributes=,
13
+ :notifications_for_followed_topics_attributes=,
14
+ :notifications_for_private_topics_attributes=,
14
15
  to: :user_preference
15
16
 
16
17
  delegate :follow_topics_on_mention, :follow_topics_on_mention=,
17
- :followed_topic_emails, :followed_topic_emails=,
18
18
  to: :user_messageboard_preference,
19
19
  prefix: :messageboard
20
20
 
@@ -36,6 +36,25 @@ module Thredded
36
36
  true
37
37
  end
38
38
 
39
+ def notifications_for_private_topics
40
+ for_every_notifier(user_preference.notifications_for_private_topics)
41
+ end
42
+
43
+ def notifications_for_followed_topics
44
+ for_every_notifier(user_preference.notifications_for_followed_topics)
45
+ end
46
+
47
+ def messageboard_notifications_for_followed_topics
48
+ return nil unless messageboard
49
+ for_every_notifier(user_preference.messageboard_notifications_for_followed_topics.for_messageboard(messageboard))
50
+ end
51
+
52
+ def for_every_notifier(prefs)
53
+ Thredded.notifiers.map do |notifier|
54
+ prefs.find { |n| n.notifier_key == notifier.key } || prefs.build(notifier_key: notifier.key)
55
+ end
56
+ end
57
+
39
58
  private
40
59
 
41
60
  # @return [Thredded::UserPreference]
@@ -52,7 +71,7 @@ module Thredded
52
71
 
53
72
  def validate_children
54
73
  promote_errors(user_preference.errors) if user_preference.invalid?
55
- if messageboard && user_messageboard_preference.invalid?
74
+ if messageboard && user_messageboard_preference.invalid? # rubocop:disable Style/GuardClause
56
75
  promote_errors(user_messageboard_preference.errors, :messageboard)
57
76
  end
58
77
  end
@@ -52,11 +52,21 @@ module Thredded
52
52
  def time_ago(datetime, default: '-')
53
53
  timeago_tag datetime,
54
54
  lang: I18n.locale.to_s.downcase,
55
- format: -> (t, _opts) { t.year == Time.current.year ? :short : :long },
55
+ format: ->(t, _opts) { t.year == Time.current.year ? :short : :long },
56
56
  nojs: true,
57
+ date_only: false,
57
58
  default: default
58
59
  end
59
60
 
61
+ # Override the default timeago_tag_content from rails-timeago
62
+ def timeago_tag_content(time, time_options = {})
63
+ if time_options[:nojs] && (time_options[:limit].nil? || time_options[:limit] < time)
64
+ t 'thredded.time_ago', time: time_ago_in_words(time)
65
+ else
66
+ I18n.l time.to_date, format: time_options[:format]
67
+ end
68
+ end
69
+
60
70
  def paginate(collection, args = {})
61
71
  super(collection, args.reverse_merge(views_prefix: 'thredded'))
62
72
  end
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require_dependency 'thredded/topic_email_view'
3
2
  module Thredded
4
3
  class PostMailer < Thredded::BaseMailer
5
4
  def post_notification(post_id, emails)
6
- @post = find_record Post, post_id
7
- email_details = TopicEmailView.new(@post.postable)
5
+ @post = find_record Thredded::Post, post_id
6
+ email_details = Thredded::TopicEmailView.new(@post.postable)
8
7
  headers['X-SMTPAPI'] = email_details.smtp_api_tag('post_notification')
9
8
 
10
9
  mail from: email_details.no_reply,
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require_dependency 'thredded/topic_email_view'
3
2
  module Thredded
4
3
  class PrivateTopicMailer < Thredded::BaseMailer
5
4
  def message_notification(private_topic_id, emails)
6
- @topic = find_record PrivateTopic, private_topic_id
7
- email_details = TopicEmailView.new(@topic)
5
+ @topic = find_record Thredded::PrivateTopic, private_topic_id
6
+ email_details = Thredded::TopicEmailView.new(@topic)
8
7
  headers['X-SMTPAPI'] = email_details.smtp_api_tag('private_topic_mailer')
9
8
 
10
9
  mail from: email_details.no_reply,
@@ -5,7 +5,7 @@ module Thredded
5
5
  # @api private
6
6
  module ContentModerationState
7
7
  extend ActiveSupport::Concern
8
- include ModerationState
8
+ include Thredded::ModerationState
9
9
 
10
10
  included do
11
11
  before_validation :set_default_moderation_state, on: :create
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ module NotifierPreference
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ delegate :human_name, to: :notifier, prefix: true
8
+
9
+ def self.detect_or_default(prefs, notifier)
10
+ (prefs && prefs.find { |pref| pref.notifier_key == notifier.key }) || default(notifier)
11
+ end
12
+ end
13
+
14
+ def notifier
15
+ @notifier ||= Thredded.notifiers.find { |notifier| notifier.key == notifier_key }
16
+ end
17
+ end
18
+ end
@@ -28,10 +28,14 @@ module Thredded
28
28
 
29
29
  # @param view_context [Object] the context of the rendering view.
30
30
  # @return [String] formatted and sanitized html-safe post content.
31
- def filtered_content(view_context, users_provider: -> (names) { readers_from_user_names(names) })
31
+ def filtered_content(view_context, users_provider: ->(names) { readers_from_user_names(names) })
32
32
  Thredded::ContentFormatter.new(view_context, users_provider: users_provider).format_content(content)
33
33
  end
34
34
 
35
+ def first_post_in_topic?
36
+ postable.first_post == self
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def ensure_user_detail
@@ -10,7 +10,7 @@ module Thredded
10
10
  foreign_key: 'last_user_id'
11
11
 
12
12
  scope :order_recently_posted_first, -> { order(last_post_at: :desc, id: :desc) }
13
- scope :on_page, -> (page_num) { page(page_num) }
13
+ scope :on_page, ->(page_num) { page(page_num) }
14
14
 
15
15
  validates :hash_id, presence: true, uniqueness: true
16
16
  validates :posts_count, numericality: true
@@ -25,11 +25,11 @@ module Thredded
25
25
  end
26
26
 
27
27
  def user
28
- super || NullUser.new
28
+ super || Thredded::NullUser.new
29
29
  end
30
30
 
31
31
  def last_user
32
- super || NullUser.new
32
+ super || Thredded::NullUser.new
33
33
  end
34
34
 
35
35
  def private?
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require 'thredded/search_parser'
3
2
  module Thredded
4
3
  class TopicsSearch
5
4
  def initialize(query, scope)
6
- @terms = SearchParser.new(query).parse
5
+ @terms = Thredded::SearchParser.new(query).parse
7
6
  @scope = scope
8
7
 
9
8
  @search_categories = @search_users = @search_text = nil
@@ -12,7 +11,7 @@ module Thredded
12
11
  # @return [ActiveRecord::Relation<Thredded::Topic>]
13
12
  def search
14
13
  if categories.present?
15
- @scope = @scope.joins(:topic_categories).merge(TopicCategory.where(category_id: categories))
14
+ @scope = @scope.joins(:topic_categories).merge(Thredded::TopicCategory.where(category_id: categories))
16
15
  end
17
16
  if text.present? || users.present?
18
17
  [search_topics, search_posts].compact.reduce(:union)
@@ -81,6 +81,17 @@ module Thredded
81
81
  order(topics_count: :desc)
82
82
  }
83
83
  # rubocop:enable Style/Lambda
84
+
85
+ # Finds the messageboard by its slug or ID, or raises Thredded::Errors::MessageboardNotFound.
86
+ # @param slug_or_id [String]
87
+ # @return [Thredded::Messageboard]
88
+ # @raise [Thredded::Errors::MessageboardNotFound] if the messageboard with the given slug does not exist.
89
+ def self.friendly_find!(slug_or_id)
90
+ friendly.find(slug_or_id)
91
+ rescue ActiveRecord::RecordNotFound
92
+ raise Thredded::Errors::MessageboardNotFound
93
+ end
94
+
84
95
  def last_user
85
96
  last_topic.try(:last_user)
86
97
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class MessageboardNotificationsForFollowedTopics < ActiveRecord::Base
4
+ belongs_to :user_preference,
5
+ primary_key: :user_id,
6
+ foreign_key: :user_id,
7
+ inverse_of: :messageboard_notifications_for_followed_topics
8
+ belongs_to :user,
9
+ class_name: Thredded.user_class,
10
+ inverse_of: :thredded_user_messageboard_preferences
11
+ belongs_to :messageboard
12
+ scope :for_messageboard, ->(messageboard) { where(messageboard_id: messageboard.id) }
13
+
14
+ validates :user_id, presence: true
15
+ validates :messageboard_id, presence: true
16
+
17
+ def self.in(messageboard)
18
+ where(messageboard_id: messageboard.id)
19
+ end
20
+
21
+ include Thredded::NotifierPreference
22
+
23
+ def self.default(_notifier)
24
+ # could be moved to `notifier.defaults(:notifications_for_followed_topics)` or
25
+ # `notifier.defaults(:messageboard_notifications_for_followed_topics)`
26
+ Thredded::BaseNotifier::NotificationsDefault.new(true)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class NotificationsForFollowedTopics < ActiveRecord::Base
4
+ belongs_to :user,
5
+ class_name: Thredded.user_class,
6
+ inverse_of: :thredded_notifications_for_followed_topics
7
+ belongs_to :messageboard # or is global
8
+ belongs_to :user_preference,
9
+ primary_key: :user_id,
10
+ foreign_key: :user_id,
11
+ inverse_of: :notifications_for_followed_topics
12
+
13
+ validates :user_id, presence: true
14
+
15
+ include Thredded::NotifierPreference
16
+
17
+ def self.default(_notifier)
18
+ # could be moved to `notifier.defaults(:notifications_for_followed_topics)`
19
+ Thredded::BaseNotifier::NotificationsDefault.new(true)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class NotificationsForPrivateTopics < ActiveRecord::Base
4
+ belongs_to :user,
5
+ class_name: Thredded.user_class,
6
+ inverse_of: :thredded_notifications_for_private_topics
7
+ belongs_to :user_preference,
8
+ primary_key: :user_id,
9
+ foreign_key: :user_id,
10
+ inverse_of: :notifications_for_private_topics
11
+
12
+ validates :user_id, presence: true
13
+
14
+ include Thredded::NotifierPreference
15
+
16
+ def self.default(_notifier)
17
+ # could be moved to `notifier.defaults(:notifications_for_private_topics)`
18
+ Thredded::BaseNotifier::NotificationsDefault.new(true)
19
+ end
20
+ end
21
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class Post < ActiveRecord::Base
4
- include PostCommon
5
- include ContentModerationState
4
+ include Thredded::PostCommon
5
+ include Thredded::ContentModerationState
6
6
 
7
7
  belongs_to :user,
8
8
  class_name: Thredded.user_class,
@@ -54,7 +54,9 @@ module Thredded
54
54
  def auto_follow_and_notify
55
55
  return unless user
56
56
  # need to do this in-process so that it appears to them immediately
57
- UserTopicFollow.create_unless_exists(user.id, postable_id, :posted)
57
+ if first_post_in_topic? ? Thredded.auto_follow_when_creating_topic : Thredded.auto_follow_when_posting_in_topic
58
+ UserTopicFollow.create_unless_exists(user.id, postable_id, :posted)
59
+ end
58
60
  # everything else can happen later
59
61
  AutoFollowAndNotifyJob.perform_later(id)
60
62
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class PostModerationRecord < ActiveRecord::Base
4
- include ModerationState
4
+ include Thredded::ModerationState
5
5
  # Rails 4 doesn't support enum _prefix
6
6
  if Rails::VERSION::MAJOR >= 5
7
7
  enum previous_moderation_state: moderation_states, _prefix: :previous
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class PrivatePost < ActiveRecord::Base
4
- include PostCommon
4
+ include Thredded::PostCommon
5
5
 
6
6
  belongs_to :user,
7
7
  class_name: Thredded.user_class,
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class PrivateTopic < ActiveRecord::Base
4
- include TopicCommon
4
+ include Thredded::TopicCommon
5
5
 
6
- scope :for_user, -> (user) { joins(:private_users).merge(PrivateUser.where(user_id: user.id)) }
6
+ scope :for_user, ->(user) { joins(:private_users).merge(PrivateUser.where(user_id: user.id)) }
7
7
 
8
8
  extend FriendlyId
9
9
  friendly_id :slug_candidates,
@@ -48,8 +48,12 @@ module Thredded
48
48
 
49
49
  before_validation :ensure_user_in_private_users
50
50
 
51
- def self.find_by_slug(slug)
52
- friendly.find(slug)
51
+ # Finds the topic by its slug or ID, or raises Thredded::Errors::PrivateTopicNotFound.
52
+ # @param slug_or_id [String]
53
+ # @return [Thredded::PrivateTopic]
54
+ # @raise [Thredded::Errors::PrivateTopicNotFound] if the topic with the given slug does not exist.
55
+ def self.friendly_find!(slug_or_id)
56
+ friendly.find(slug_or_id)
53
57
  rescue ActiveRecord::RecordNotFound
54
58
  raise Thredded::Errors::PrivateTopicNotFound
55
59
  end
@@ -18,7 +18,7 @@ module Thredded
18
18
  private
19
19
 
20
20
  def messageboards
21
- @messageboards ||= Messageboard.ordered
21
+ @messageboards ||= Thredded::Messageboard.ordered
22
22
  end
23
23
  end
24
24
  end
@@ -1,17 +1,16 @@
1
1
  # frozen_string_literal: true
2
- require 'thredded/topics_search'
3
2
  module Thredded
4
3
  class Topic < ActiveRecord::Base
5
- include TopicCommon
6
- include ContentModerationState
4
+ include Thredded::TopicCommon
5
+ include Thredded::ContentModerationState
7
6
 
8
- scope :for_messageboard, -> (messageboard) { where(messageboard_id: messageboard.id) }
7
+ scope :for_messageboard, ->(messageboard) { where(messageboard_id: messageboard.id) }
9
8
 
10
9
  scope :stuck, -> { where(sticky: true) }
11
10
  scope :unstuck, -> { where(sticky: false) }
12
11
 
13
12
  # Using `search_query` instead of `search` to avoid conflict with Ransack.
14
- scope :search_query, -> (query) { ::Thredded::TopicsSearch.new(query, self).search }
13
+ scope :search_query, ->(query) { ::Thredded::TopicsSearch.new(query, self).search }
15
14
 
16
15
  scope :order_sticky_first, -> { order(sticky: :desc) }
17
16
 
@@ -77,8 +76,12 @@ module Thredded
77
76
  after_commit :update_messageboard_last_topic, on: :update, if: -> { previous_changes.include?('moderation_state') }
78
77
  after_update :update_last_user_and_time_from_last_post!, if: -> { previous_changes.include?('moderation_state') }
79
78
 
80
- def self.find_by_slug!(slug)
81
- friendly.find(slug)
79
+ # Finds the topic by its slug or ID, or raises Thredded::Errors::TopicNotFound.
80
+ # @param slug_or_id [String]
81
+ # @return [Thredded::Topic]
82
+ # @raise [Thredded::Errors::TopicNotFound] if the topic with the given slug does not exist.
83
+ def self.friendly_find!(slug_or_id)
84
+ friendly.find(slug_or_id)
82
85
  rescue ActiveRecord::RecordNotFound
83
86
  raise Thredded::Errors::TopicNotFound
84
87
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class UserDetail < ActiveRecord::Base
4
- include ModerationState
4
+ include Thredded::ModerationState
5
5
 
6
6
  belongs_to :user, class_name: Thredded.user_class, inverse_of: :thredded_user_detail
7
7
  validates :user_id, presence: true, uniqueness: true
@@ -9,7 +9,7 @@ module Thredded
9
9
  include ::Thredded::UserPermissions::Moderate::IfModeratorColumnTrue
10
10
  include ::Thredded::UserPermissions::Admin::IfAdminColumnTrue
11
11
 
12
- included do
12
+ included do # rubocop:disable Metrics/BlockLength
13
13
  with_options dependent: :nullify, foreign_key: 'user_id', inverse_of: :user do |opt|
14
14
  opt.has_many :thredded_posts, class_name: 'Thredded::Post'
15
15
  opt.has_many :thredded_topics, class_name: 'Thredded::Topic'
@@ -24,6 +24,8 @@ module Thredded
24
24
 
25
25
  with_options dependent: :destroy, foreign_key: 'user_id', inverse_of: :user do |opt|
26
26
  opt.has_many :thredded_user_messageboard_preferences, class_name: 'Thredded::UserMessageboardPreference'
27
+ opt.has_many :thredded_notifications_for_followed_topics, class_name: 'Thredded::NotificationsForFollowedTopics'
28
+ opt.has_many :thredded_notifications_for_private_topics, class_name: 'Thredded::NotificationsForPrivateTopics'
27
29
  opt.has_many :thredded_private_users, class_name: 'Thredded::PrivateUser'
28
30
  opt.has_many :thredded_topic_read_states, class_name: 'Thredded::UserTopicReadState'
29
31
  opt.has_many :thredded_private_topic_read_states, class_name: 'Thredded::UserPrivateTopicReadState'
@@ -2,11 +2,22 @@
2
2
  module Thredded
3
3
  class UserPreference < ActiveRecord::Base
4
4
  belongs_to :user, class_name: Thredded.user_class, inverse_of: :thredded_user_preference
5
- has_many :messageboard_preferences,
6
- class_name: 'Thredded::UserMessageboardPreference',
7
- primary_key: :user_id,
8
- foreign_key: :user_id,
9
- inverse_of: :user_preference
5
+
6
+ with_options(inverse_of: :user_preference, primary_key: :user_id, foreign_key: :user_id,
7
+ dependent: :destroy) do |opt|
8
+ opt.has_many :messageboard_preferences,
9
+ class_name: 'Thredded::UserMessageboardPreference'
10
+ opt.has_many :messageboard_notifications_for_followed_topics,
11
+ class_name: 'Thredded::MessageboardNotificationsForFollowedTopics'
12
+ opt.has_many :notifications_for_followed_topics,
13
+ class_name: 'Thredded::NotificationsForFollowedTopics'
14
+ opt.has_many :notifications_for_private_topics,
15
+ class_name: 'Thredded::NotificationsForPrivateTopics'
16
+ end
10
17
  validates :user_id, presence: true
18
+
19
+ accepts_nested_attributes_for :notifications_for_followed_topics,
20
+ :notifications_for_private_topics,
21
+ :messageboard_notifications_for_followed_topics
11
22
  end
12
23
  end