thredded 0.14.4 → 0.15.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.
- checksums.yaml +4 -4
- data/README.md +3 -2
- data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -0
- data/app/assets/javascripts/thredded/components/spoilers.es6 +37 -0
- data/app/assets/stylesheets/thredded/_email.scss +25 -0
- data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
- data/app/assets/stylesheets/thredded/base/_typography.scss +1 -1
- data/app/assets/stylesheets/thredded/base/_variables.scss +2 -3
- data/app/assets/stylesheets/thredded/components/_post.scss +0 -11
- data/app/assets/stylesheets/thredded/components/_spoiler.scss +41 -0
- data/app/commands/thredded/mark_all_read.rb +1 -7
- data/app/controllers/concerns/thredded/new_post_params.rb +1 -1
- data/app/controllers/concerns/thredded/new_private_post_params.rb +1 -1
- data/app/controllers/concerns/thredded/new_private_topic_params.rb +1 -2
- data/app/controllers/concerns/thredded/new_topic_params.rb +0 -1
- data/app/controllers/thredded/application_controller.rb +10 -0
- data/app/controllers/thredded/posts_controller.rb +1 -2
- data/app/controllers/thredded/private_posts_controller.rb +1 -2
- data/app/controllers/thredded/private_topics_controller.rb +10 -12
- data/app/controllers/thredded/topics_controller.rb +14 -17
- data/app/forms/thredded/edit_topic_form.rb +2 -1
- data/app/forms/thredded/post_form.rb +3 -1
- data/app/forms/thredded/private_post_form.rb +3 -1
- data/app/forms/thredded/private_topic_form.rb +1 -0
- data/app/jobs/thredded/activity_updater_job.rb +18 -8
- data/app/jobs/thredded/auto_follow_and_notify_job.rb +2 -1
- data/app/models/concerns/thredded/post_common.rb +3 -4
- data/app/models/concerns/thredded/topic_common.rb +7 -0
- data/app/models/concerns/thredded/user_topic_read_state_common.rb +62 -5
- data/app/models/thredded/null_user_topic_read_state.rb +8 -0
- data/app/policies/thredded/private_post_policy.rb +16 -0
- data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
- data/app/view_models/thredded/base_topic_view.rb +5 -1
- data/app/view_models/thredded/post_view.rb +13 -1
- data/app/view_models/thredded/posts_page_view.rb +1 -1
- data/app/view_models/thredded/topic_posts_page_view.rb +13 -1
- data/app/views/thredded/posts/_post.html.erb +1 -0
- data/app/views/thredded/posts/edit.html.erb +1 -0
- data/app/views/thredded/posts_common/_before_first_unread_post.html.erb +7 -0
- data/app/views/thredded/posts_common/form/_after_content.html.erb +8 -0
- data/app/views/thredded/posts_common/form/_before_content.html.erb +8 -0
- data/app/views/thredded/private_posts/_private_post.html.erb +2 -1
- data/app/views/thredded/private_posts/edit.html.erb +1 -0
- data/app/views/thredded/private_topics/_form.html.erb +1 -0
- data/app/views/thredded/private_topics/edit.html.erb +2 -1
- data/app/views/thredded/shared/_field_errors.html.erb +3 -0
- data/app/views/thredded/shared/_nav.html.erb +1 -1
- data/app/views/thredded/shared/_page.html.erb +1 -1
- data/app/views/thredded/topics/_form.html.erb +1 -0
- data/app/views/thredded/topics/edit.html.erb +2 -1
- data/config/locales/de.yml +2 -0
- data/config/locales/en.yml +2 -0
- data/config/locales/es.yml +2 -0
- data/config/locales/fr.yml +2 -0
- data/config/locales/it.yml +2 -0
- data/config/locales/pl.yml +2 -0
- data/config/locales/pt-BR.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/locales/zh-CN.yml +2 -0
- data/db/migrate/20160329231848_create_thredded.rb +37 -23
- data/db/upgrade_migrations/{20170811090735_upgrade_thredded_v0_13_to_v_014.rb → 20170811090735_upgrade_thredded_v0_13_to_v0_14.rb} +0 -0
- data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +91 -0
- data/lib/generators/thredded/install/templates/initializer.rb +16 -7
- data/lib/thredded.rb +143 -125
- data/lib/thredded/arel_compat.rb +57 -0
- data/lib/thredded/base_migration.rb +10 -0
- data/lib/thredded/collection_to_strings_with_cache_renderer.rb +35 -9
- data/lib/thredded/content_formatter.rb +27 -18
- data/lib/thredded/database_seeder.rb +218 -64
- data/lib/thredded/email_transformer.rb +5 -2
- data/lib/thredded/email_transformer/spoiler.rb +25 -0
- data/lib/thredded/formatting_demo_content.rb +12 -0
- data/lib/thredded/html_pipeline/onebox_filter.rb +3 -38
- data/lib/thredded/html_pipeline/spoiler_tag_filter.rb +128 -0
- data/lib/thredded/html_pipeline/utils.rb +47 -0
- data/lib/thredded/rails_lt_5_2_arel_case_node.rb +119 -0
- data/lib/thredded/version.rb +1 -1
- metadata +17 -21
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
|
4
|
+
require 'thredded/rails_lt_5_2_arel_case_node.rb'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Thredded
|
8
|
+
module ArelCompat
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# @param [#connection] engine
|
12
|
+
# @param [Arel::Nodes::Node] a integer node
|
13
|
+
# @param [Arel::Nodes::Node] b integer node
|
14
|
+
# @return [Arel::Nodes::Node] a / b
|
15
|
+
def integer_division(engine, a, b)
|
16
|
+
if engine.connection.adapter_name =~ /mysql|mariadb/i
|
17
|
+
Arel::Nodes::InfixOperation.new('DIV', a, b)
|
18
|
+
else
|
19
|
+
Arel::Nodes::Division.new(a, b)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
|
24
|
+
# @param [ActiveRecord::Relation] relation
|
25
|
+
# @return [Arel::Nodes::Node]
|
26
|
+
def relation_to_arel(relation)
|
27
|
+
relation.arel
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def relation_to_arel(relation)
|
31
|
+
Arel.sql("(#{relation.to_sql})")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if Rails::VERSION::MAJOR >= 5
|
36
|
+
# @param [Arel::Nodes::Node] table
|
37
|
+
# @return [Arel::SelectManager]
|
38
|
+
def new_arel_select_manager(table)
|
39
|
+
Arel::SelectManager.new(table)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
def new_arel_select_manager(table)
|
43
|
+
Arel::SelectManager.new(ActiveRecord::Base, table)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
|
48
|
+
def true_value(_engine)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
else
|
52
|
+
def true_value(engine)
|
53
|
+
engine.connection.adapter_name =~ /sqlite|mysql|mariadb/i ? 1 : true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module Thredded
|
4
4
|
class BaseMigration < (Thredded.rails_gte_51? ? ActiveRecord::Migration[5.1] : ActiveRecord::Migration)
|
5
|
+
protected
|
6
|
+
|
5
7
|
def user_id_type
|
6
8
|
Thredded.user_class.columns.find { |c| c.name == Thredded.user_class.primary_key }.sql_type
|
7
9
|
end
|
@@ -10,5 +12,13 @@ module Thredded
|
|
10
12
|
column_name = column_name.to_s
|
11
13
|
columns(table).find { |c| c.name == column_name }.sql_type
|
12
14
|
end
|
15
|
+
|
16
|
+
# @return [Integer, nil] the maximum number of codepoints that can be indexed for a primary key or index.
|
17
|
+
def max_key_length
|
18
|
+
return nil unless connection.adapter_name =~ /mysql|maria/i
|
19
|
+
# Conservatively assume that innodb_large_prefix is **disabled**.
|
20
|
+
# If it were enabled, the maximum key length would instead be 768 utf8mb4 characters.
|
21
|
+
191
|
22
|
+
end
|
13
23
|
end
|
14
24
|
end
|
@@ -1,15 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'action_view/renderer/abstract_renderer'
|
4
|
+
|
4
5
|
module Thredded
|
5
6
|
class CollectionToStringsWithCacheRenderer < ActionView::AbstractRenderer
|
7
|
+
class << self
|
8
|
+
# The default number of threads to use for rendering.
|
9
|
+
attr_accessor :render_threads
|
10
|
+
end
|
11
|
+
|
12
|
+
self.render_threads = 50
|
13
|
+
|
6
14
|
# @param view_context
|
7
|
-
# @param
|
8
|
-
# @param
|
9
|
-
# @param
|
15
|
+
# @param [Array<T>] collection
|
16
|
+
# @param [String] partial
|
17
|
+
# @param [ActiveSupport::Duration] expires_in
|
18
|
+
# @param [Integer] render_threads the number of threads to use for rendering. This is useful even on MRI ruby
|
19
|
+
# for IO-bound operations.
|
20
|
+
# @param [Hash] locals
|
10
21
|
# @return Array<[T, String]>
|
11
22
|
def render_collection_to_strings_with_cache( # rubocop:disable Metrics/ParameterLists
|
12
|
-
view_context, collection:, partial:, expires_in:, locals: {}, **opts
|
23
|
+
view_context, collection:, partial:, expires_in:, render_threads: self.class.render_threads, locals: {}, **opts
|
13
24
|
)
|
14
25
|
template = @lookup_context.find_template(partial, [], true, locals, {})
|
15
26
|
collection = collection.to_a
|
@@ -28,7 +39,9 @@ module Thredded
|
|
28
39
|
|
29
40
|
collection_to_render = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
|
30
41
|
rendered_partials = render_partials(
|
31
|
-
view_context,
|
42
|
+
view_context,
|
43
|
+
collection: collection_to_render, render_threads: render_threads,
|
44
|
+
partial: partial, locals: locals, **opts
|
32
45
|
).each
|
33
46
|
|
34
47
|
keyed_collection.map do |cache_key, item|
|
@@ -52,12 +65,25 @@ module Thredded
|
|
52
65
|
end
|
53
66
|
|
54
67
|
# @return [Array<String>]
|
55
|
-
def render_partials(view_context, collection:, **opts)
|
68
|
+
def render_partials(view_context, collection:, render_threads:, **opts)
|
56
69
|
return [] if collection.empty?
|
57
|
-
|
58
|
-
|
59
|
-
|
70
|
+
num_threads = [render_threads, collection.size].min
|
71
|
+
if num_threads == 1
|
72
|
+
render_partials_serial(view_context, collection, opts)
|
73
|
+
else
|
74
|
+
collection.each_slice(collection.size / num_threads).map do |slice|
|
75
|
+
Thread.start { render_partials_serial(view_context.dup, slice, opts) }
|
76
|
+
end.flat_map(&:value)
|
60
77
|
end
|
61
78
|
end
|
79
|
+
|
80
|
+
# @param [Array<Object>] collection
|
81
|
+
# @param [Hash] opts
|
82
|
+
# @param view_context
|
83
|
+
# @return [Array<String>]
|
84
|
+
def render_partials_serial(view_context, collection, opts)
|
85
|
+
partial_renderer = ActionView::PartialRenderer.new(@lookup_context)
|
86
|
+
collection.map { |object| partial_renderer.render(view_context, opts.merge(object: object), nil) }
|
87
|
+
end
|
62
88
|
end
|
63
89
|
end
|
@@ -3,8 +3,30 @@
|
|
3
3
|
module Thredded
|
4
4
|
# Generates HTML from content source.
|
5
5
|
class ContentFormatter
|
6
|
-
|
7
|
-
|
6
|
+
class << self
|
7
|
+
# Sanitization whitelist options.
|
8
|
+
attr_accessor :whitelist
|
9
|
+
|
10
|
+
# Filters that run before processing the markup.
|
11
|
+
# input: markup, output: markup.
|
12
|
+
attr_accessor :before_markup_filters
|
13
|
+
|
14
|
+
# Markup filters, such as BBCode, Markdown, Autolink, etc.
|
15
|
+
# input: markup, output: html.
|
16
|
+
attr_accessor :markup_filters
|
17
|
+
|
18
|
+
# Filters that run after processing the markup.
|
19
|
+
# input: html, output: html.
|
20
|
+
attr_accessor :after_markup_filters
|
21
|
+
|
22
|
+
# Filters that sanitize the resulting HTML.
|
23
|
+
# input: html, output: sanitized html.
|
24
|
+
attr_accessor :sanitization_filters
|
25
|
+
|
26
|
+
# Filters that run after sanitization
|
27
|
+
# input: sanitized html, output: html
|
28
|
+
attr_accessor :after_sanitization_filters
|
29
|
+
end
|
8
30
|
|
9
31
|
self.whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
|
10
32
|
elements: HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w[abbr iframe span figure figcaption],
|
@@ -25,44 +47,31 @@ module Thredded
|
|
25
47
|
'span' => %w[class],
|
26
48
|
'div' => %w[class],
|
27
49
|
:all => HTML::Pipeline::SanitizationFilter::WHITELIST[:attributes][:all] +
|
28
|
-
%w[aria-label aria-labelledby aria-hidden],
|
50
|
+
%w[aria-expanded aria-label aria-labelledby aria-live aria-hidden aria-pressed role],
|
29
51
|
}
|
30
52
|
)
|
31
53
|
|
32
|
-
# Filters that run before processing the markup.
|
33
|
-
# input: markup, output: markup.
|
34
|
-
mattr_accessor :before_markup_filters
|
35
54
|
self.before_markup_filters = [
|
55
|
+
Thredded::HtmlPipeline::SpoilerTagFilter::BeforeMarkup
|
36
56
|
]
|
37
57
|
|
38
|
-
# Markup filters, such as BBCode, Markdown, Autolink, etc.
|
39
|
-
# input: markup, output: html.
|
40
|
-
mattr_accessor :markup_filters
|
41
58
|
self.markup_filters = [
|
42
59
|
Thredded::HtmlPipeline::KramdownFilter,
|
43
60
|
]
|
44
61
|
|
45
|
-
# Filters that run after processing the markup.
|
46
|
-
# input: html, output: html.
|
47
|
-
mattr_accessor :after_markup_filters
|
48
62
|
self.after_markup_filters = [
|
49
63
|
# AutolinkFilter is required because Kramdown does not autolink by default.
|
50
64
|
# https://github.com/gettalong/kramdown/issues/306
|
51
65
|
Thredded::HtmlPipeline::AutolinkFilter,
|
52
66
|
HTML::Pipeline::EmojiFilter,
|
53
67
|
Thredded::HtmlPipeline::AtMentionFilter,
|
68
|
+
Thredded::HtmlPipeline::SpoilerTagFilter::AfterMarkup,
|
54
69
|
]
|
55
70
|
|
56
|
-
# Filters that sanitize the resulting HTML.
|
57
|
-
# input: html, output: sanitized html.
|
58
|
-
mattr_accessor :sanitization_filters
|
59
71
|
self.sanitization_filters = [
|
60
72
|
HTML::Pipeline::SanitizationFilter,
|
61
73
|
]
|
62
74
|
|
63
|
-
# Filters that run after sanitization
|
64
|
-
# input: sanitized html, output: html
|
65
|
-
mattr_accessor :after_sanitization_filters
|
66
75
|
self.after_sanitization_filters = [
|
67
76
|
Thredded::HtmlPipeline::OneboxFilter,
|
68
77
|
Thredded::HtmlPipeline::WrapIframesFilter,
|
@@ -12,40 +12,128 @@ rescue NameError
|
|
12
12
|
end
|
13
13
|
# rubocop:enable HandleExceptions
|
14
14
|
module Thredded
|
15
|
-
class DatabaseSeeder
|
15
|
+
class DatabaseSeeder # rubocop:disable Metrics/ClassLength
|
16
|
+
module LogTime
|
17
|
+
def self.included(base)
|
18
|
+
base.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_time
|
22
|
+
start = Time.now.to_f
|
23
|
+
result = yield
|
24
|
+
print_time_diff start
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_time_diff(from, to = Time.now.to_f)
|
29
|
+
log " [#{format('%.2f', to - from)}s]\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def log_method_time(method_name)
|
34
|
+
prepend(Module.new do
|
35
|
+
define_method method_name do |*args, **kwargs|
|
36
|
+
log_time { super(*args, **kwargs) }
|
37
|
+
end
|
38
|
+
end)
|
39
|
+
method_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
include LogTime
|
45
|
+
|
16
46
|
SKIP_CALLBACKS = [
|
17
|
-
[Thredded::Post, :commit, :after, :
|
18
|
-
[Thredded::
|
47
|
+
[Thredded::Post, :commit, :after, :update_parent_last_user_and_time_from_last_post, on: %i[create destroy]],
|
48
|
+
[Thredded::Post, :commit, :after, :update_parent_last_user_and_time_from_last_post_if_moderation_state_changed,
|
49
|
+
on: :update],
|
50
|
+
[Thredded::Post, :commit, :after, :auto_follow_and_notify, on: %i[create update]],
|
51
|
+
[Thredded::PrivatePost, :commit, :after, :update_parent_last_user_and_timestamp, on: %i[create destroy]],
|
52
|
+
[Thredded::PrivatePost, :commit, :after, :notify_users, on: [:create]],
|
53
|
+
].freeze
|
54
|
+
DISABLE_COUNTER_CACHE = [Thredded::Post, Thredded::PrivatePost].freeze
|
55
|
+
WRITEABLE_READONLY_ATTRIBUTES = [
|
56
|
+
[Thredded::Topic, 'posts_count'],
|
57
|
+
[Thredded::PrivateTopic, 'posts_count'],
|
19
58
|
].freeze
|
20
59
|
|
21
|
-
|
22
|
-
|
60
|
+
# Applies global tweaks required to run seeder methods for the given block.
|
61
|
+
def self.with_seeder_tweaks
|
23
62
|
# Disable callbacks to avoid creating notifications and performing unnecessary updates
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
s.seed(users: users, topics: topics, posts: posts)
|
28
|
-
s.log 'Running after_commit callbacks'
|
63
|
+
DISABLE_COUNTER_CACHE.each do |klass|
|
64
|
+
klass.send(:alias_method, :original_each_counter_cached_associations, :each_counter_cached_associations)
|
65
|
+
klass.send(:define_method, :each_counter_cached_associations) {}
|
29
66
|
end
|
67
|
+
SKIP_CALLBACKS.each { |(klass, *args)| delete_callbacks(klass, *args) }
|
68
|
+
WRITEABLE_READONLY_ATTRIBUTES.each { |(klass, attr)| klass.readonly_attributes.delete(attr) }
|
69
|
+
logger_was = ActiveRecord::Base.logger
|
70
|
+
ActiveRecord::Base.logger = nil
|
71
|
+
yield
|
30
72
|
ensure
|
31
|
-
# Re-enable callbacks
|
32
|
-
|
73
|
+
# Re-enable callbacks and counter cache
|
74
|
+
DISABLE_COUNTER_CACHE.each do |klass|
|
75
|
+
klass.send(:remove_method, :each_counter_cached_associations)
|
76
|
+
klass.send(:alias_method, :each_counter_cached_associations, :original_each_counter_cached_associations)
|
77
|
+
klass.send(:remove_method, :original_each_counter_cached_associations)
|
78
|
+
end
|
79
|
+
SKIP_CALLBACKS.each do |(klass, *args)|
|
80
|
+
args = args.dup
|
81
|
+
klass.send(:set_options_for_callbacks!, args)
|
82
|
+
klass.set_callback(*args)
|
83
|
+
end
|
84
|
+
WRITEABLE_READONLY_ATTRIBUTES.each { |(klass, attr)| klass.readonly_attributes << attr }
|
85
|
+
ActiveRecord::Base.logger = logger_was
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.delete_callbacks(klass, name, *filter_list, &block)
|
89
|
+
type, filters, _options = klass.normalize_callback_params(filter_list, block)
|
90
|
+
klass.__update_callbacks(name) do |target, chain|
|
91
|
+
filters.each do |filter|
|
92
|
+
chain.delete(chain.find { |c| c.matches?(type, filter) })
|
93
|
+
end
|
94
|
+
target.send :set_callbacks, name, chain
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.run(**kwargs)
|
99
|
+
new.run(**kwargs)
|
33
100
|
end
|
34
101
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
102
|
+
def run(users: 200, topics: 70, posts: (1..70))
|
103
|
+
log "Seeding the database...\n"
|
104
|
+
self.class.with_seeder_tweaks do
|
105
|
+
t_txn_0 = nil
|
106
|
+
Messageboard.transaction do
|
107
|
+
initialize_fake_post_contents(topics: topics, posts: posts)
|
108
|
+
users(count: users)
|
109
|
+
first_messageboard
|
110
|
+
topics(count: topics)
|
111
|
+
private_topics(count: topics)
|
112
|
+
posts(count: posts)
|
113
|
+
private_posts(count: posts)
|
114
|
+
create_additional_messageboards
|
115
|
+
follow_some_topics
|
116
|
+
read_some_topics(count: (topics / 4..topics / 3))
|
117
|
+
update_messageboards_data
|
118
|
+
t_txn_0 = Time.now.to_f
|
119
|
+
log 'Committing transaction and running after_commit callbacks'
|
120
|
+
end
|
121
|
+
print_time_diff t_txn_0
|
122
|
+
end
|
45
123
|
end
|
46
124
|
|
47
125
|
def log(message)
|
48
|
-
STDERR.
|
126
|
+
STDERR.write "- #{message}"
|
127
|
+
STDERR.flush
|
128
|
+
end
|
129
|
+
|
130
|
+
log_method_time def initialize_fake_post_contents(topics:, posts:)
|
131
|
+
log 'Initializing fake post contents...'
|
132
|
+
@fake_post_contents = Array.new([topics * (posts.min + posts.max) / 2, 1000].min) { FakeContent.post_content }
|
133
|
+
end
|
134
|
+
|
135
|
+
def fake_post_contents
|
136
|
+
@fake_post_contents ? @fake_post_contents.sample : FakeContent.post_content
|
49
137
|
end
|
50
138
|
|
51
139
|
def first_user
|
@@ -56,6 +144,12 @@ module Thredded
|
|
56
144
|
@users ||= Users.new(self).find_or_create(count: count)
|
57
145
|
end
|
58
146
|
|
147
|
+
def user_details
|
148
|
+
@user_details ||= users.each_with_object({}) do |user, hash|
|
149
|
+
hash[user] = user.thredded_user_detail
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
59
153
|
def first_messageboard
|
60
154
|
@first_messageboard ||= FirstMessageboard.new(self).find_or_create
|
61
155
|
end
|
@@ -68,10 +162,19 @@ module Thredded
|
|
68
162
|
'Need help using the forum? Want to report a bug or make a suggestion? This is the place.', meta_group_id],
|
69
163
|
['Praise', 'Want to tell us how great we are? This is the place.', meta_group_id]
|
70
164
|
]
|
71
|
-
log "Creating #{additional_messageboards.length} additional messageboards
|
165
|
+
log "Creating #{additional_messageboards.length} additional messageboards...\n"
|
72
166
|
additional_messageboards.each do |(name, description, group_id)|
|
73
167
|
messageboard = Messageboard.create!(name: name, description: description, messageboard_group_id: group_id)
|
74
|
-
|
168
|
+
topics = Topics.new(self).create(count: 1 + rand(3), messageboard: messageboard)
|
169
|
+
Posts.new(self).create(count: (1..2), topics: topics)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
log_method_time def update_messageboards_data(**) # `**` for Ruby < 2.5, see https://bugs.ruby-lang.org/issues/10856
|
174
|
+
log 'Updating messageboards data...'
|
175
|
+
Messageboard.all.each do |messageboard|
|
176
|
+
messageboard.update_last_topic!
|
177
|
+
Thredded::Messageboard.reset_counters(messageboard.id, :posts)
|
75
178
|
end
|
76
179
|
end
|
77
180
|
|
@@ -91,10 +194,12 @@ module Thredded
|
|
91
194
|
@private_posts ||= PrivatePosts.new(self).find_or_create(count: count)
|
92
195
|
end
|
93
196
|
|
94
|
-
def follow_some_topics(count: (5..10), count_users: (1..5))
|
197
|
+
log_method_time def follow_some_topics(count: (5..10), count_users: (1..5))
|
95
198
|
log 'Following some topics...'
|
96
199
|
posts.each do |post|
|
97
|
-
|
200
|
+
next unless post.user_id
|
201
|
+
Thredded::UserTopicFollow.create_with(reason: :posted)
|
202
|
+
.find_or_create_by(user_id: post.user_id, topic_id: post.postable_id)
|
98
203
|
end
|
99
204
|
follow_some_topics_by_user(first_user, count: count)
|
100
205
|
users.sample(count_users.min + rand(count_users.max - count_users.min + 2)).each do |user|
|
@@ -104,11 +209,11 @@ module Thredded
|
|
104
209
|
|
105
210
|
def follow_some_topics_by_user(user, count: (1..10))
|
106
211
|
topics.sample(count.min + rand(count.max - count.min + 2)).each do |topic|
|
107
|
-
Thredded::UserTopicFollow.
|
212
|
+
Thredded::UserTopicFollow.create_with(reason: :manual).find_or_create_by(user_id: user.id, topic_id: topic.id)
|
108
213
|
end
|
109
214
|
end
|
110
215
|
|
111
|
-
def read_some_topics(count: (5..10), count_users: (1..5))
|
216
|
+
log_method_time def read_some_topics(count: (5..10), count_users: (1..5))
|
112
217
|
log 'Reading some topics...'
|
113
218
|
topics.each do |topic|
|
114
219
|
read_topic(topic, topic.last_user_id) if topic.last_user_id
|
@@ -126,11 +231,18 @@ module Thredded
|
|
126
231
|
end
|
127
232
|
|
128
233
|
def read_topic(topic, user_id)
|
129
|
-
Thredded::UserTopicReadState.find_or_initialize_by(user_id: user_id, postable_id: topic.id)
|
130
|
-
|
234
|
+
read_state = Thredded::UserTopicReadState.find_or_initialize_by(user_id: user_id, postable_id: topic.id)
|
235
|
+
if rand(2).zero?
|
236
|
+
read_state.update!(read_at: topic.updated_at)
|
237
|
+
else
|
238
|
+
read_state.update!(read_at: topic.posts.order_newest_first.first(2).last.created_at)
|
239
|
+
end
|
131
240
|
end
|
132
241
|
|
133
242
|
class BaseSeedData
|
243
|
+
include LogTime
|
244
|
+
|
245
|
+
# @return [Thredded::DatabaseSeeder]
|
134
246
|
attr_reader :seeder
|
135
247
|
|
136
248
|
def initialize(seed_database = DatabaseSeeder.new)
|
@@ -149,6 +261,11 @@ module Thredded
|
|
149
261
|
@stored = (find || create(*args))
|
150
262
|
end
|
151
263
|
|
264
|
+
def range_of_dates_in_order(up_to: Time.zone.now, count: 1)
|
265
|
+
written = up_to
|
266
|
+
Array.new(count - 1) { written -= random_duration(10.minutes..6.hours) }.reverse + [up_to]
|
267
|
+
end
|
268
|
+
|
152
269
|
protected
|
153
270
|
|
154
271
|
def model_class
|
@@ -164,6 +281,10 @@ module Thredded
|
|
164
281
|
def find
|
165
282
|
fail 'Unimplemented'
|
166
283
|
end
|
284
|
+
|
285
|
+
def random_duration(range)
|
286
|
+
(range.min.to_i + rand(range.max.to_i)).seconds
|
287
|
+
end
|
167
288
|
end
|
168
289
|
|
169
290
|
class FirstSeedData < BaseSeedData
|
@@ -183,7 +304,6 @@ module Thredded
|
|
183
304
|
MODEL_CLASS = User
|
184
305
|
|
185
306
|
def create
|
186
|
-
log 'Creating first user...'
|
187
307
|
FactoryBot.create(:user, :approved, :admin, name: 'Joe', email: 'joe@example.com')
|
188
308
|
end
|
189
309
|
end
|
@@ -192,19 +312,19 @@ module Thredded
|
|
192
312
|
class Users < CollectionSeedData
|
193
313
|
MODEL_CLASS = User
|
194
314
|
|
195
|
-
def create(count: 1)
|
315
|
+
log_method_time def create(count: 1)
|
196
316
|
log "Creating #{count} users..."
|
197
317
|
approved_users_count = (count * 0.97).round
|
198
318
|
[seeder.first_user] +
|
199
|
-
|
200
|
-
|
319
|
+
FactoryBot.create_list(:user, approved_users_count, :approved, :with_user_details) +
|
320
|
+
FactoryBot.create_list(:user, count - approved_users_count, :with_user_details)
|
201
321
|
end
|
202
322
|
end
|
203
323
|
|
204
324
|
class FirstMessageboard < FirstSeedData
|
205
325
|
MODEL_CLASS = Messageboard
|
206
326
|
|
207
|
-
def create
|
327
|
+
log_method_time def create(**) # `**` for Ruby < 2.5, see https://bugs.ruby-lang.org/issues/10856
|
208
328
|
log 'Creating a messageboard...'
|
209
329
|
@first_messageboard = FactoryBot.create(
|
210
330
|
:messageboard,
|
@@ -218,28 +338,39 @@ module Thredded
|
|
218
338
|
class Topics < CollectionSeedData
|
219
339
|
MODEL_CLASS = Topic
|
220
340
|
|
221
|
-
def create(count: 1, messageboard: seeder.first_messageboard)
|
341
|
+
log_method_time def create(count: 1, messageboard: seeder.first_messageboard)
|
222
342
|
log "Creating #{count} topics in #{messageboard.name}..."
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
343
|
+
Array.new(count) do
|
344
|
+
FactoryBot.build(
|
345
|
+
:topic,
|
346
|
+
messageboard: messageboard,
|
347
|
+
user: seeder.users.sample,
|
348
|
+
last_user: seeder.users.sample
|
349
|
+
).tap do |topic|
|
350
|
+
topic.user_detail = seeder.user_details[topic.user]
|
351
|
+
topic.send :set_slug
|
352
|
+
topic.send :set_default_moderation_state
|
353
|
+
topic.save(validate: false)
|
354
|
+
end
|
355
|
+
end
|
229
356
|
end
|
230
357
|
end
|
231
358
|
|
232
359
|
class PrivateTopics < CollectionSeedData
|
233
360
|
MODEL_CLASS = PrivateTopic
|
234
361
|
|
235
|
-
def create(count: 1)
|
362
|
+
log_method_time def create(count: 1)
|
363
|
+
log "Creating #{count} private topics..."
|
236
364
|
Array.new(count) do
|
237
|
-
FactoryBot.
|
365
|
+
FactoryBot.build(
|
238
366
|
:private_topic,
|
239
367
|
user: seeder.users[1..-1].sample,
|
240
368
|
last_user: seeder.users.sample,
|
241
369
|
users: [seeder.first_user, *seeder.users.sample(1 + rand(3))]
|
242
|
-
)
|
370
|
+
).tap do |topic|
|
371
|
+
topic.send :set_slug
|
372
|
+
topic.save(validate: false)
|
373
|
+
end
|
243
374
|
end
|
244
375
|
end
|
245
376
|
end
|
@@ -247,41 +378,64 @@ module Thredded
|
|
247
378
|
class Posts < CollectionSeedData
|
248
379
|
MODEL_CLASS = Post
|
249
380
|
|
250
|
-
def create(count: (1..1))
|
381
|
+
log_method_time def create(count: (1..1), topics: seeder.topics) # rubocop:disable Metrics/MethodLength
|
251
382
|
log "Creating #{count} additional posts in each topic..."
|
252
|
-
|
253
|
-
last_post_at = random_duration(0..
|
383
|
+
topics.flat_map do |topic|
|
384
|
+
last_post_at = random_duration(0..256.hours).ago
|
254
385
|
posts_count = (count.min + rand(count.max + 1))
|
255
386
|
posts = range_of_dates_in_order(up_to: last_post_at, count: posts_count).map.with_index do |written_at, i|
|
256
387
|
author = i.zero? ? topic.user : seeder.users.sample
|
257
|
-
|
258
|
-
|
388
|
+
Post.new(
|
389
|
+
content: seeder.fake_post_contents,
|
390
|
+
messageboard_id: topic.messageboard_id,
|
391
|
+
postable: topic,
|
392
|
+
user: author,
|
393
|
+
user_detail: seeder.user_details[author],
|
394
|
+
created_at: written_at,
|
395
|
+
updated_at: written_at,
|
396
|
+
).tap do |post|
|
397
|
+
post.send :set_default_moderation_state
|
398
|
+
post.save(validate: false)
|
399
|
+
end
|
259
400
|
end
|
260
|
-
topic.
|
401
|
+
topic.update_columns(
|
402
|
+
posts_count: posts_count,
|
403
|
+
last_user_id: posts.last.user.id,
|
404
|
+
updated_at: last_post_at,
|
405
|
+
last_post_at: last_post_at
|
406
|
+
)
|
261
407
|
posts
|
262
408
|
end
|
263
409
|
end
|
264
|
-
|
265
|
-
def range_of_dates_in_order(up_to: Time.zone.now, count: 1)
|
266
|
-
written = up_to
|
267
|
-
Array.new(count - 1) { written -= random_duration(10.minutes..6.hours) }.reverse + [up_to]
|
268
|
-
end
|
269
|
-
|
270
|
-
def random_duration(range)
|
271
|
-
(range.min.to_i + rand(range.max.to_i)).seconds
|
272
|
-
end
|
273
410
|
end
|
274
411
|
|
275
412
|
class PrivatePosts < CollectionSeedData
|
276
413
|
MODEL_CLASS = PrivatePost
|
277
414
|
|
278
|
-
def create(count: (1..1))
|
415
|
+
log_method_time def create(count: (1..1))
|
279
416
|
log "Creating #{count} additional posts in each private topic..."
|
280
417
|
seeder.private_topics.flat_map do |topic|
|
281
|
-
|
418
|
+
last_post_at = random_duration(0..256.hours).ago
|
419
|
+
posts_count = (count.min + rand(count.max + 1))
|
420
|
+
posts = range_of_dates_in_order(up_to: last_post_at, count: posts_count).map.with_index do |written_at, i|
|
282
421
|
author = i.zero? ? topic.user : topic.users.sample
|
283
|
-
|
422
|
+
PrivatePost.new(
|
423
|
+
postable: topic,
|
424
|
+
user: author,
|
425
|
+
created_at: written_at,
|
426
|
+
updated_at: written_at,
|
427
|
+
content: seeder.fake_post_contents,
|
428
|
+
).tap do |post|
|
429
|
+
post.save(validate: false)
|
430
|
+
end
|
284
431
|
end
|
432
|
+
topic.update_columns(
|
433
|
+
posts_count: posts_count,
|
434
|
+
last_user_id: posts.last.user.id,
|
435
|
+
updated_at: last_post_at,
|
436
|
+
last_post_at: last_post_at
|
437
|
+
)
|
438
|
+
posts
|
285
439
|
end
|
286
440
|
end
|
287
441
|
end
|