yiffspace 0.0.1 → 0.0.3
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/lib/yiffspace/concerns/active_record_extensions.rb +45 -0
- data/lib/yiffspace/concerns/api_methods.rb +101 -0
- data/lib/yiffspace/concerns/attribute_matchers.rb +100 -0
- data/lib/yiffspace/concerns/attribute_methods.rb +39 -0
- data/lib/yiffspace/concerns/concurrency_methods.rb +20 -0
- data/lib/yiffspace/concerns/conditional_includes.rb +43 -0
- data/lib/yiffspace/concerns/current_methods.rb +60 -0
- data/lib/yiffspace/concerns/has_bit_flags.rb +59 -0
- data/lib/yiffspace/concerns/user_methods.rb +77 -0
- data/lib/yiffspace/concerns/user_name_methods.rb +53 -0
- data/lib/yiffspace/configuration.rb +70 -10
- data/lib/yiffspace/core_ext/all.rb +0 -1
- data/lib/yiffspace/include/all.rb +16 -0
- data/lib/yiffspace/include/cache.rb +5 -0
- data/lib/yiffspace/include/current.rb +5 -0
- data/lib/yiffspace/include/duration_parser.rb +5 -0
- data/lib/yiffspace/include/helpers.rb +5 -0
- data/lib/yiffspace/{core_ext → include}/open_hash.rb +2 -0
- data/lib/yiffspace/include/parameter_builder.rb +5 -0
- data/lib/yiffspace/include/parse_value.rb +5 -0
- data/lib/yiffspace/include/query_builder.rb +5 -0
- data/lib/yiffspace/include/query_dsl.rb +5 -0
- data/lib/yiffspace/include/query_helper.rb +5 -0
- data/lib/yiffspace/include/routes.rb +5 -0
- data/lib/yiffspace/include/table_builder.rb +5 -0
- data/lib/yiffspace/include/trace_logger.rb +5 -0
- data/lib/yiffspace/include/user_attribute.rb +5 -0
- data/lib/yiffspace/search/query_builder.rb +83 -0
- data/lib/yiffspace/search/query_dsl.rb +119 -0
- data/lib/yiffspace/search/query_helper.rb +49 -0
- data/lib/yiffspace/utils/cache.rb +40 -0
- data/lib/yiffspace/utils/current.rb +43 -0
- data/lib/yiffspace/utils/duration_parser.rb +24 -0
- data/lib/yiffspace/utils/helpers.rb +20 -0
- data/lib/yiffspace/utils/parameter_builder.rb +121 -0
- data/lib/yiffspace/utils/parse_value.rb +174 -0
- data/lib/yiffspace/utils/routes.rb +28 -0
- data/lib/yiffspace/utils/table_builder.rb +136 -0
- data/lib/yiffspace/utils/trace_logger.rb +91 -0
- data/lib/yiffspace/utils/user_attribute.rb +271 -0
- data/lib/yiffspace/version.rb +1 -1
- data/lib/yiffspace.rb +11 -1
- metadata +68 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aca3308a25bbf6b426ea3a8d52a9bfe7a41452cc7e500332accab52ee0d2fde0
|
|
4
|
+
data.tar.gz: 2cac0c503de3fa0141b0a923c0fadeb4225b01640b74dac33c8f743127a5273e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62946e5dfb3a8593ae125882c845652babac22b5d4b55cb421a8de1cf6503b1940ada9b1e00b640b3d3d0af4f8f8867be832c0600e4f2de18f5c7acff71b8182
|
|
7
|
+
data.tar.gz: bb2435f4fd6d6f7ab4b4d6d4110e90b4bf1aceb36109db94af99078f652a59acfdc569fbe28b57367de014aa5443d9b413ad3ecdceced070321979a2b5537ccd
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module ActiveRecordExtensions
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def without_timeout
|
|
10
|
+
original = statement_timeout
|
|
11
|
+
connection.execute("SET STATEMENT_TIMEOUT = 0") unless Rails.env.test?
|
|
12
|
+
yield
|
|
13
|
+
ensure
|
|
14
|
+
connection.execute("SET STATEMENT_TIMEOUT = #{original}") unless Rails.env.test?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with_timeout(time, default_value = nil)
|
|
18
|
+
original = statement_timeout
|
|
19
|
+
connection.execute("SET STATEMENT_TIMEOUT = #{time}") unless Rails.env.test?
|
|
20
|
+
yield
|
|
21
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
|
22
|
+
Rails.logger.warn("Statement timeout exceeded: #{e.message}")
|
|
23
|
+
default_value
|
|
24
|
+
ensure
|
|
25
|
+
connection.execute("SET STATEMENT_TIMEOUT = #{original}") unless Rails.env.test?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def statement_timeout
|
|
29
|
+
ApplicationRecord.connection.select_one("SELECT setting FROM pg_settings WHERE name = 'statement_timeout'")["setting"].to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# CrossJoinLateral, LeftJoinLateral, nil
|
|
33
|
+
def unnest(column, name: column.singularize, type: Arel::Nodes::LeftJoinLateral)
|
|
34
|
+
function = Arel::Nodes::NamedFunction.new("unnest", [arel(column)], name)
|
|
35
|
+
return function if type.nil?
|
|
36
|
+
|
|
37
|
+
joins(type.new(
|
|
38
|
+
function,
|
|
39
|
+
Arel::Nodes::On.new(Arel.sql("TRUE")),
|
|
40
|
+
))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module ApiMethods
|
|
6
|
+
class NotVisibleError < StandardError; end
|
|
7
|
+
extend(ActiveSupport::Concern)
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def available_includes
|
|
11
|
+
[]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def multiple_includes
|
|
15
|
+
reflections.select { |_, v| v.macro == :has_many }.keys.map(&:to_sym)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def associated_models(name)
|
|
19
|
+
if reflections[name].options[:polymorphic]
|
|
20
|
+
reflections[name].active_record.try(:model_types) || []
|
|
21
|
+
else
|
|
22
|
+
[reflections[name].class_name]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
delegate(:available_includes, to: :class)
|
|
28
|
+
|
|
29
|
+
# XXX deprecated, shouldn't expose this as an instance method.
|
|
30
|
+
def api_attributes(user)
|
|
31
|
+
policy = Pundit.policy(user, self) || ApplicationPolicy.new(user, self)
|
|
32
|
+
policy.api_attributes
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# XXX deprecated, shouldn't expose this as an instance method.
|
|
36
|
+
def html_data_attributes(user)
|
|
37
|
+
policy = Pundit.policy(user, self) || ApplicationPolicy.new(user, self)
|
|
38
|
+
policy.html_data_attributes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process_api_attributes(options, underscore: false)
|
|
42
|
+
options[:methods] ||= []
|
|
43
|
+
attributes, methods = api_attributes(options[:user]).partition { |attr| has_attribute?(attr) }
|
|
44
|
+
methods += options[:methods]
|
|
45
|
+
if underscore && options[:only].blank?
|
|
46
|
+
options[:only] = attributes + methods
|
|
47
|
+
else
|
|
48
|
+
options[:only] ||= attributes + methods
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attributes &= options[:only]
|
|
52
|
+
methods &= options[:only]
|
|
53
|
+
|
|
54
|
+
options[:only] = attributes
|
|
55
|
+
options[:methods] = methods
|
|
56
|
+
|
|
57
|
+
options.delete(:methods) if options[:methods].empty?
|
|
58
|
+
options
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def serializable_hash(options = {})
|
|
62
|
+
options ||= {}
|
|
63
|
+
options[:user] ||= CurrentUser.user || User.anonymous
|
|
64
|
+
return :not_visible unless visible?(options[:user])
|
|
65
|
+
|
|
66
|
+
if options[:only].is_a?(String)
|
|
67
|
+
options.delete(:methods)
|
|
68
|
+
options.delete(:include)
|
|
69
|
+
options.merge!(ParameterBuilder.serial_parameters(options[:only], self, options))
|
|
70
|
+
if options[:only].include?("_")
|
|
71
|
+
options[:only].delete("_")
|
|
72
|
+
options = process_api_attributes(options, underscore: true)
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
options = process_api_attributes(options)
|
|
76
|
+
end
|
|
77
|
+
options[:only] += [SecureRandom.hex(6)]
|
|
78
|
+
|
|
79
|
+
hash = super
|
|
80
|
+
hash.transform_keys! { |key| key.delete("?").delete_prefix("apionly_") }
|
|
81
|
+
deep_reject_hash(hash) { |_, v| v == :not_visible }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def visible?(_user)
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def deep_reject_hash(hash, &block)
|
|
89
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
90
|
+
if value.is_a?(Hash)
|
|
91
|
+
result[key] = deep_reject_hash(value, &block)
|
|
92
|
+
elsif value.is_a?(Array)
|
|
93
|
+
result[key] = value.map { |v| v.is_a?(Hash) ? deep_reject_hash(v, &block) : v }.reject { |i| block.call(nil, i) }
|
|
94
|
+
elsif !block.call(key, value)
|
|
95
|
+
result[key] = value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module AttributeMatchers
|
|
6
|
+
def boolean_attribute_matches(attribute, value, &)
|
|
7
|
+
return all if value.nil?
|
|
8
|
+
|
|
9
|
+
if value.to_s.truthy?
|
|
10
|
+
value = true
|
|
11
|
+
elsif value.to_s.falsy?
|
|
12
|
+
value = false
|
|
13
|
+
else
|
|
14
|
+
raise(ArgumentError, "value must be truthy or falsy")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
q = block_given? ? yield : all
|
|
18
|
+
q.where(attribute => value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# range: "5", ">5", "<5", ">=5", "<=5", "5..10", "5,6,7"
|
|
22
|
+
def numeric_attribute_matches(attribute, value, &)
|
|
23
|
+
return all if value.nil?
|
|
24
|
+
|
|
25
|
+
value = value.to_s.strip
|
|
26
|
+
column = QueryHelper.get_column(attribute, table_name)
|
|
27
|
+
parsed_range = ParseValue.range(value, column.type)
|
|
28
|
+
|
|
29
|
+
add_range_relation(parsed_range, attribute, &)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# datetime columns return ActiveSupport::TimeWithZone
|
|
33
|
+
def datetime_attribute_matches(attribute, range, &)
|
|
34
|
+
numeric_attribute_matches(attribute, range, &)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def add_range_relation(arr, field, &)
|
|
38
|
+
return all if arr.nil?
|
|
39
|
+
|
|
40
|
+
q = block_given? ? yield : all
|
|
41
|
+
case arr[0]
|
|
42
|
+
when :eq
|
|
43
|
+
if arr[1].is_a?(Time)
|
|
44
|
+
q.where.between(field => arr[1].all_day)
|
|
45
|
+
else
|
|
46
|
+
q.where(field => arr[1])
|
|
47
|
+
end
|
|
48
|
+
when :gt
|
|
49
|
+
q.where.gt(field => arr[1])
|
|
50
|
+
when :gte
|
|
51
|
+
q.where.gte(field => arr[1])
|
|
52
|
+
when :lt
|
|
53
|
+
q.where.lt(field => arr[1])
|
|
54
|
+
when :lte
|
|
55
|
+
q.where.lte(field => arr[1])
|
|
56
|
+
when :in
|
|
57
|
+
q.where.in(field => arr[1])
|
|
58
|
+
when :between
|
|
59
|
+
q.where.between(field => arr[1]..arr[2])
|
|
60
|
+
else
|
|
61
|
+
q.none
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def text_attribute_matches(attribute, value, convert_to_wildcard: false, &)
|
|
66
|
+
return all if value.nil?
|
|
67
|
+
|
|
68
|
+
value = "*#{value}*" if convert_to_wildcard && value.exclude?("*")
|
|
69
|
+
q = block_given? ? yield : all
|
|
70
|
+
if value.is_a?(Array)
|
|
71
|
+
q.where.ilike_any(attribute => value)
|
|
72
|
+
elsif value =~ /\*/
|
|
73
|
+
q.where.ilike(attribute => value)
|
|
74
|
+
else
|
|
75
|
+
q.where.tsquery(attribute => value)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ip_attribute_matches(attribute, value, &)
|
|
80
|
+
return all if value.nil?
|
|
81
|
+
|
|
82
|
+
q = block_given? ? yield : all
|
|
83
|
+
q.where.inet_contained_within_or_equals(attribute => value)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def attribute_present(attribute, value, &)
|
|
87
|
+
return all if value.nil?
|
|
88
|
+
|
|
89
|
+
q = block_given? ? yield : all
|
|
90
|
+
if value.to_s.truthy?
|
|
91
|
+
q.where.not(attribute => nil)
|
|
92
|
+
elsif value.to_s.falsy?
|
|
93
|
+
q.where(attribute => nil)
|
|
94
|
+
else
|
|
95
|
+
q.none
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module AttributeMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# Defines `<attribute>_string`, `<attribute>_string=`, and `<attribute>=`
|
|
10
|
+
# methods for converting an array attribute to or from a string.
|
|
11
|
+
#
|
|
12
|
+
# The `<attribute>=` setter parses strings into an array using the
|
|
13
|
+
# `parse` regex. The resulting strings can be converted to another type
|
|
14
|
+
# with the `cast` option.
|
|
15
|
+
def array_attribute(name, parse: /[^[:space:]]+/, join_character: " ", cast: :itself)
|
|
16
|
+
define_method("#{name}_string") do
|
|
17
|
+
send(name).join(join_character)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
define_method("#{name}_string=") do |value|
|
|
21
|
+
raise(ArgumentError, "#{name} must be a String") unless value.respond_to?(:to_str)
|
|
22
|
+
|
|
23
|
+
send("#{name}=", value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
define_method("#{name}=") do |value|
|
|
27
|
+
if value.respond_to?(:to_str)
|
|
28
|
+
super(value.to_str.scan(parse).flatten.map(&cast))
|
|
29
|
+
elsif value.respond_to?(:to_a)
|
|
30
|
+
super(value.to_a)
|
|
31
|
+
else
|
|
32
|
+
raise(ArgumentError, "#{name} must be a String or an Array")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module ConcurrencyMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def parallel_find_each(**, &)
|
|
10
|
+
# XXX We may deadlock if a transaction is open; do a non-parallel each.
|
|
11
|
+
return find_each(&) if connection.transaction_open?
|
|
12
|
+
|
|
13
|
+
find_in_batches(error_on_ignore: true, **) do |batch|
|
|
14
|
+
batch.parallel_each(&)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module ConditionalIncludes
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def html_includes(request, *)
|
|
10
|
+
includes_if(request.format.html?, *)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def includes_if(condition, *)
|
|
14
|
+
return all unless condition
|
|
15
|
+
|
|
16
|
+
includes(*)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def includes_unless(condition, *)
|
|
20
|
+
return all if condition
|
|
21
|
+
|
|
22
|
+
includes(*)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def html_preload(request, *)
|
|
26
|
+
preload_if(request.format.html?, *)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def preload_if(condition, *)
|
|
30
|
+
return all unless condition
|
|
31
|
+
|
|
32
|
+
preload(*)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def preload_unless(condition, *)
|
|
36
|
+
return all if condition
|
|
37
|
+
|
|
38
|
+
preload(*)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module CurrentMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def search_current(params, ...)
|
|
10
|
+
search(params, YiffSpace.config.current_class.user, ...)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def with_current(instance, method, *args, **, &)
|
|
14
|
+
attr = args.grep(Symbol)
|
|
15
|
+
other = args - attr
|
|
16
|
+
hashes = other.grep(Hash)
|
|
17
|
+
params = other.grep(ActionController::Parameters)
|
|
18
|
+
other -= hashes + params
|
|
19
|
+
other.compact_blank!
|
|
20
|
+
options = [attr.index_with(YiffSpace.config.current_class.user), *hashes, *params.map(&:to_hash)].inject(:merge)
|
|
21
|
+
instance.send(method, *other, **options, **, &)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def new_with_current(...)
|
|
25
|
+
with_current(self, :new, ...)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_with_current(...)
|
|
29
|
+
with_current(self, :create, ...)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_with_current!(...)
|
|
33
|
+
with_current(self, :create!, ...)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update_with_current(...)
|
|
38
|
+
self.class.with_current(self, :update, ...)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update_with_current!(...)
|
|
42
|
+
self.class.with_current(self, :update!, ...)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroy_with_current(*attrs)
|
|
46
|
+
attrs.each do |attr|
|
|
47
|
+
send("#{attr}=", YiffSpace.config.current_class.user)
|
|
48
|
+
end
|
|
49
|
+
destroy
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def destroy_with_current!(*attrs)
|
|
53
|
+
attrs.each do |attr|
|
|
54
|
+
send("#{attr}=", YiffSpace.config.current_class.user)
|
|
55
|
+
end
|
|
56
|
+
destroy!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module HasBitFlags
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# NOTE: the ordering of attributes has to be fixed
|
|
10
|
+
# new attributes should be appended to the end.
|
|
11
|
+
def has_bit_flags(attributes, options = {})
|
|
12
|
+
field = options[:field] || :bit_flags
|
|
13
|
+
|
|
14
|
+
define_singleton_method("flag_value_for") do |key|
|
|
15
|
+
value = attributes[key.to_s]
|
|
16
|
+
return value if value
|
|
17
|
+
|
|
18
|
+
raise(ArgumentError, "Invalid flag: #{key}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attributes.each do |name, value|
|
|
22
|
+
scope(name.to_sym, -> { where.has_bits(field => value) })
|
|
23
|
+
define_method(name) do
|
|
24
|
+
send(field) & value == value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
define_method("#{name}=") do |val|
|
|
28
|
+
if val.to_s =~ /[t1y]/
|
|
29
|
+
send("#{field}=", send(field) | value)
|
|
30
|
+
else
|
|
31
|
+
send("#{field}=", send(field) & ~value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
define_method("#{name}_was") do
|
|
36
|
+
send("#{field}_was") & value == value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
define_method("#{name}_before_last_save") do
|
|
40
|
+
send("#{field}_before_last_save") & value == value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
alias_method("#{name}?", name)
|
|
44
|
+
alias_method("#{name}_before_last_save?", "#{name}_before_last_save")
|
|
45
|
+
alias_method("#{name}_was?", "#{name}_was")
|
|
46
|
+
|
|
47
|
+
define_method("#{name}_changed?") do
|
|
48
|
+
send("#{name}_was") != send(name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
define_method("saved_change_to_#{name}?") do
|
|
52
|
+
send("#{name}_before_last_save") != send(name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module UserMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def belongs_to_user(*, **)
|
|
10
|
+
UserAttribute.new(self, *, **, db: true)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resolvable(*, **)
|
|
14
|
+
UserAttribute.new(self, *, **, db: false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with_user(instance, method, user, *args, **, &)
|
|
18
|
+
default = { new: :creator, create: :creator, create!: :creator, update: :updater, update!: :updater, destroy: :destroyer, destroy!: :destroyer }.fetch(method)
|
|
19
|
+
attrs = args.grep(Symbol)
|
|
20
|
+
attrs = [default] if default && attrs.empty?
|
|
21
|
+
other = args - attrs
|
|
22
|
+
hashes = other.grep(Hash)
|
|
23
|
+
params = other.grep(ActionController::Parameters)
|
|
24
|
+
other -= hashes + params
|
|
25
|
+
other.compact_blank!
|
|
26
|
+
options = [attrs.index_with(user), *hashes, *params.map(&:to_hash)].inject(:merge)
|
|
27
|
+
if %i[destroy destroy!].include?(method)
|
|
28
|
+
options.each do |attr, value|
|
|
29
|
+
instance.send("#{attr}=", value)
|
|
30
|
+
end
|
|
31
|
+
instance.send(method)
|
|
32
|
+
else
|
|
33
|
+
instance.send(method, *other, **options, **, &)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def new_with(...)
|
|
38
|
+
with_user(self, :new, ...)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_with(...)
|
|
42
|
+
new_with(...).tap(&:save)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_with!(...)
|
|
46
|
+
new_with(...).tap(&:save!)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update_with(...)
|
|
51
|
+
self.class.with_user(self, :update, ...)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update_with!(...)
|
|
55
|
+
self.class.with_user(self, :update!, ...)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def destroy_with(...)
|
|
59
|
+
self.class.with_user(self, :destroy, ...)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def destroy_with!(...)
|
|
63
|
+
self.class.with_user(self, :destroy!, ...)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def put(attr, value, overwrite: false)
|
|
69
|
+
if respond_to?(:"#{attr}_id")
|
|
70
|
+
send(:"#{attr}=", value) if overwrite || send(:"#{attr}_id").blank?
|
|
71
|
+
elsif respond_to?(attr)
|
|
72
|
+
send(:"#{attr}=", value) if overwrite || send(attr).blank?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module UserNameMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def name_to_id(name)
|
|
10
|
+
normalized_name = normalize_name(name)
|
|
11
|
+
Cache.fetch("uni:#{normalized_name}", expires_in: 4.hours) do
|
|
12
|
+
::User.where("lower(name) = ?", normalized_name).pick(:id)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param arguments [Array<String, Array<String>>] a list of names
|
|
17
|
+
# @return [Hash{String => Integer, nil}] a hash of normalized names to user IDs
|
|
18
|
+
def bulk_name_to_id(*arguments)
|
|
19
|
+
names = arguments.flatten.map { |n| normalize_name(n) }
|
|
20
|
+
results = names.index_with { |name| Utils::Cache.fetch("uni:#{name}") }.compact_blank
|
|
21
|
+
missing = names - results.keys
|
|
22
|
+
fetched = ::User.where("lower(name) IN (?)", missing).select(:id, :name).to_h { |u| [normalize_name(u.name), u.id] }
|
|
23
|
+
not_found = (missing - fetched.keys).index_with { nil }
|
|
24
|
+
fetched.each { |name, id| Utils::Cache.write("uni:#{name}", id, expires_in: 4.hours) }
|
|
25
|
+
{ **results, **fetched, **not_found }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def name_or_id_to_id(name)
|
|
29
|
+
return name[1..].to_i if name =~ /\A!\d+\z/
|
|
30
|
+
|
|
31
|
+
::User.name_to_id(name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def name_or_id_to_id_forced(name)
|
|
35
|
+
return name.to_i if name =~ /\A\d+\z/
|
|
36
|
+
|
|
37
|
+
::User.name_to_id(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def id_to_name(user_id)
|
|
41
|
+
RequestStore[:id_name_cache] ||= {}
|
|
42
|
+
return RequestStore[:id_name_cache][user_id] if RequestStore[:id_name_cache].key?(user_id)
|
|
43
|
+
|
|
44
|
+
name = Cache.fetch("uin:#{user_id}", expires_in: 4.hours) do
|
|
45
|
+
::User.where(id: user_id).pick(:name) || YiffSpace.config.anonymous_user_name.call
|
|
46
|
+
end
|
|
47
|
+
RequestStore[:id_name_cache][user_id] = name
|
|
48
|
+
name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|