yiffspace 0.0.1 → 0.0.2
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 +24 -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 +32 -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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Utils
|
|
5
|
+
class ParameterBuilder
|
|
6
|
+
def self.serial_parameters(only_string, object, options = {})
|
|
7
|
+
only_array = split_only_string(only_string)
|
|
8
|
+
get_only_hash(only_array, object, options)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.get_only_hash(only_array, object, options = {}, seen_objects = [])
|
|
12
|
+
return {} if object.nil?
|
|
13
|
+
|
|
14
|
+
is_root = seen_objects.empty?
|
|
15
|
+
only_hash = { only: [], include: [], methods: [] }
|
|
16
|
+
available_includes = object.available_includes
|
|
17
|
+
attributes, methods = object.api_attributes(options[:user]).partition { |attr| object.has_attribute?(attr) }
|
|
18
|
+
methods -= available_includes
|
|
19
|
+
# Attributes and/or methods may be included in the final pass, but not includes
|
|
20
|
+
seen_objects << object.class.name
|
|
21
|
+
underscore = false
|
|
22
|
+
only_array.each do |item|
|
|
23
|
+
if item == "_"
|
|
24
|
+
underscore = true
|
|
25
|
+
next
|
|
26
|
+
end
|
|
27
|
+
match = item.match(/(\w+)\[(.+?)\]$/)
|
|
28
|
+
item = (match || [])[1] || item
|
|
29
|
+
item_sym = item.to_sym
|
|
30
|
+
was_seen = inclusion_seen?(item, object.class, seen_objects)
|
|
31
|
+
if match && available_includes.include?(item_sym) && (!was_seen || is_root)
|
|
32
|
+
item_object = object.send(item_sym)
|
|
33
|
+
next if item_object.nil?
|
|
34
|
+
|
|
35
|
+
item_object = item_object[0] if item_object.is_a?(ActiveRecord::Relation)
|
|
36
|
+
item_array = split_only_string(match[2])
|
|
37
|
+
item_hash = get_only_hash(item_array, item_object, options, seen_objects.clone)
|
|
38
|
+
only_hash[:include] << { item_sym => item_hash }
|
|
39
|
+
elsif available_includes.include?(item_sym) && (!was_seen || is_root)
|
|
40
|
+
only_hash[:include] << item_sym
|
|
41
|
+
elsif attributes.include?(item_sym)
|
|
42
|
+
only_hash[:only] << item_sym
|
|
43
|
+
elsif methods.include?(item_sym)
|
|
44
|
+
only_hash[:methods] << item_sym
|
|
45
|
+
only_hash[:only] << item_sym
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
only_hash.delete(:include) if only_hash[:include].empty?
|
|
49
|
+
only_hash.delete(:methods) if only_hash[:methods].empty?
|
|
50
|
+
only_hash[:only].unshift("_") if underscore
|
|
51
|
+
only_hash
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.includes_parameters(only_string, model_name)
|
|
55
|
+
return [] if only_string.blank?
|
|
56
|
+
|
|
57
|
+
only_array = split_only_string(only_string)
|
|
58
|
+
get_includes_array(only_array, model_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.get_includes_array(only_array, model_name, seen_objects = [])
|
|
62
|
+
is_root = seen_objects.empty?
|
|
63
|
+
include_array = []
|
|
64
|
+
model = Kernel.const_get(model_name)
|
|
65
|
+
available_includes = model.available_includes
|
|
66
|
+
# Attributes and/or methods may be included in the final pass, but not includes
|
|
67
|
+
seen_objects << model_name
|
|
68
|
+
only_array.each do |item|
|
|
69
|
+
match = item.match(/(\w+)\[(.+?)\]$/)
|
|
70
|
+
item = (match || [])[1] || item
|
|
71
|
+
item_sym = item.to_sym
|
|
72
|
+
was_seen = inclusion_seen?(item, model, seen_objects)
|
|
73
|
+
if match && available_includes.include?(item_sym) && (!was_seen || is_root)
|
|
74
|
+
item_array = split_only_string(match[2])
|
|
75
|
+
model.associated_models(item).each do |m|
|
|
76
|
+
item_array = get_includes_array(item_array, m, seen_objects.clone)
|
|
77
|
+
include_array << (item_array.empty? ? item_sym : { item_sym => item_array })
|
|
78
|
+
end
|
|
79
|
+
elsif available_includes.include?(item_sym) && (!was_seen || is_root)
|
|
80
|
+
include_array << item_sym
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
include_array
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.inclusion_seen?(inclusion, class_object, seen_objects)
|
|
87
|
+
if class_object.reflections[inclusion]
|
|
88
|
+
inclusion_class = class_object.reflections[inclusion].class_name
|
|
89
|
+
max_seen = (class_object.multiple_includes.include?(inclusion.to_sym) ? 1 : 0)
|
|
90
|
+
seen_objects.count(inclusion_class) > max_seen
|
|
91
|
+
else
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.split_only_string(only_string)
|
|
97
|
+
only_array = []
|
|
98
|
+
offset = 0
|
|
99
|
+
position = 0
|
|
100
|
+
level = 0
|
|
101
|
+
loop do
|
|
102
|
+
str = only_string[Range.new(position, -1)]
|
|
103
|
+
match = str.match(/[,\[\]]/)
|
|
104
|
+
break unless match
|
|
105
|
+
|
|
106
|
+
start_pos, end_pos = match.offset(0)
|
|
107
|
+
if match[0] == "," && level.zero?
|
|
108
|
+
only_array << only_string[Range.new(offset, position + start_pos - 1)]
|
|
109
|
+
offset = position + end_pos
|
|
110
|
+
elsif match[0] == "["
|
|
111
|
+
level += 1
|
|
112
|
+
elsif match[0] == "]"
|
|
113
|
+
level -= 1
|
|
114
|
+
end
|
|
115
|
+
position += end_pos
|
|
116
|
+
end
|
|
117
|
+
only_array << only_string[Range.new(offset, -1)]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Utils
|
|
5
|
+
module ParseValue
|
|
6
|
+
MAX_INT = 2_147_483_647
|
|
7
|
+
MIN_INT = -2_147_483_648
|
|
8
|
+
extend(self)
|
|
9
|
+
|
|
10
|
+
def date_range(target)
|
|
11
|
+
case target
|
|
12
|
+
# 10_yesterweeks_ago, 10yesterweekago
|
|
13
|
+
when /\A(\d{1,2})_?yester(week|month|year)s?_?ago\z/
|
|
14
|
+
yester_range($1.to_i, $2)
|
|
15
|
+
when /\Ayester(week|month|year)\z/
|
|
16
|
+
yester_range(1, $1)
|
|
17
|
+
when /\A(day|week|month|year)\z/
|
|
18
|
+
[:gte, Time.zone.now - 1.send($1)]
|
|
19
|
+
# 10_weeks_ago, 10w
|
|
20
|
+
when /\A(\d+)_?(s(econds?)?|mi(nutes?)?|h(ours?)?|d(ays?)?|w(eeks?)?|mo(nths?)?|y(ears?)?)_?(ago)?\z/i
|
|
21
|
+
[:gte, time_string(target)]
|
|
22
|
+
else
|
|
23
|
+
range(target, :date)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def range_fudged(range, type)
|
|
28
|
+
result = range(range, type)
|
|
29
|
+
if result[0] == :eq
|
|
30
|
+
new_min = [(result[1] * 0.95).to_i, MIN_INT].max
|
|
31
|
+
new_max = [(result[1] * 1.05).to_i, MAX_INT].min
|
|
32
|
+
[:between, new_min, new_max]
|
|
33
|
+
else
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def range(range, type = :integer)
|
|
39
|
+
if range.start_with?("<=")
|
|
40
|
+
[:lte, cast(range.delete_prefix("<="), type)]
|
|
41
|
+
|
|
42
|
+
elsif range.start_with?("..")
|
|
43
|
+
[:lte, cast(range.delete_prefix(".."), type)]
|
|
44
|
+
|
|
45
|
+
elsif range.start_with?("<")
|
|
46
|
+
[:lt, cast(range.delete_prefix("<"), type)]
|
|
47
|
+
|
|
48
|
+
elsif range.start_with?(">=")
|
|
49
|
+
[:gte, cast(range.delete_prefix(">="), type)]
|
|
50
|
+
|
|
51
|
+
elsif range.end_with?("..")
|
|
52
|
+
[:gte, cast(range.delete_suffix(".."), type)]
|
|
53
|
+
|
|
54
|
+
elsif range.start_with?(">")
|
|
55
|
+
[:gt, cast(range.delete_prefix(">"), type)]
|
|
56
|
+
|
|
57
|
+
elsif range.include?("..")
|
|
58
|
+
left, right = range.split("..", 2)
|
|
59
|
+
[:between, cast(left, type), cast(right, type)]
|
|
60
|
+
|
|
61
|
+
elsif range.include?(",")
|
|
62
|
+
[:in, range.split(",").first(YiffSpace.config.max_multi_count.call).map { |x| cast(x, type) }]
|
|
63
|
+
|
|
64
|
+
else
|
|
65
|
+
[:eq, cast(range, type)]
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
RANGE_INVERSIONS = {
|
|
71
|
+
lte: :gte,
|
|
72
|
+
lt: :gt,
|
|
73
|
+
gte: :lte,
|
|
74
|
+
gt: :lt,
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
77
|
+
def invert_range(range)
|
|
78
|
+
# >10 <=> <10
|
|
79
|
+
range[0] = RANGE_INVERSIONS[range[0]] || range[0]
|
|
80
|
+
# 10..20 <=> 20..10
|
|
81
|
+
range[1], range[2] = range[2], range[1] if range[0] == :between
|
|
82
|
+
range
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def cast(object, type)
|
|
88
|
+
case type
|
|
89
|
+
when :integer
|
|
90
|
+
object.to_i.clamp(MIN_INT, MAX_INT)
|
|
91
|
+
|
|
92
|
+
when :float
|
|
93
|
+
# Floats obviously have a different range but this is good enough
|
|
94
|
+
object.to_f.clamp(MIN_INT, MAX_INT)
|
|
95
|
+
|
|
96
|
+
when :date, :datetime
|
|
97
|
+
case object
|
|
98
|
+
when "today"
|
|
99
|
+
return Date.current
|
|
100
|
+
when "yesterday"
|
|
101
|
+
return Date.yesterday
|
|
102
|
+
when "decade"
|
|
103
|
+
return Date.current - 10.years
|
|
104
|
+
when /\A(day|week|month|year)\z/
|
|
105
|
+
return Date.current - 1.send($1.to_sym)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
ago = time_string(object)
|
|
109
|
+
return ago if ago.present?
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
Time.zone.parse(object)
|
|
113
|
+
rescue ArgumentError
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
when :age
|
|
118
|
+
time_string(object)
|
|
119
|
+
|
|
120
|
+
when :ratio
|
|
121
|
+
left, right = object.split(":", 10)
|
|
122
|
+
|
|
123
|
+
if right && right.to_f != 0.0
|
|
124
|
+
(left.to_f / right.to_f).round(10)
|
|
125
|
+
elsif right
|
|
126
|
+
0.0
|
|
127
|
+
else
|
|
128
|
+
object.to_f.round(2)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
when :filesize
|
|
132
|
+
size = object.downcase
|
|
133
|
+
if size.end_with?("kb")
|
|
134
|
+
size.to_f.kilobytes
|
|
135
|
+
elsif size.end_with?("mb")
|
|
136
|
+
size.to_f.megabytes
|
|
137
|
+
else
|
|
138
|
+
size.to_f
|
|
139
|
+
end.to_i
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def yester_range(count, unit)
|
|
144
|
+
origin = Date.current - count.send(unit)
|
|
145
|
+
start = origin.send("beginning_of_#{unit}")
|
|
146
|
+
stop = origin.send("end_of_#{unit}")
|
|
147
|
+
[:between, start, stop]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def time_string(target)
|
|
151
|
+
target =~ /\A(\d+)_?(s(econds?)?|mi(nutes?)?|h(ours?)?|d(ays?)?|w(eeks?)?|mo(nths?)?|y(ears?)?)_?(ago)?\z/i
|
|
152
|
+
|
|
153
|
+
size = $1.to_i
|
|
154
|
+
unit = $2&.downcase || ""
|
|
155
|
+
|
|
156
|
+
if unit.start_with?("s")
|
|
157
|
+
size.seconds.ago
|
|
158
|
+
elsif unit.start_with?("mi")
|
|
159
|
+
size.minutes.ago
|
|
160
|
+
elsif unit.start_with?("h")
|
|
161
|
+
size.hours.ago
|
|
162
|
+
elsif unit.start_with?("d")
|
|
163
|
+
size.days.ago
|
|
164
|
+
elsif unit.start_with?("w")
|
|
165
|
+
size.weeks.ago
|
|
166
|
+
elsif unit.start_with?("mo")
|
|
167
|
+
size.months.ago
|
|
168
|
+
elsif unit.start_with?("y")
|
|
169
|
+
size.years.ago
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Utils
|
|
5
|
+
# Allow Rails URL helpers to be used outside of views.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# Routes.posts_path(tags: "male")
|
|
9
|
+
# => "/posts?tags=male"
|
|
10
|
+
#
|
|
11
|
+
# @see config/routes.rb
|
|
12
|
+
# @see https://guides.rubyonrails.org/routing.html
|
|
13
|
+
module Routes
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def method_missing(name, *, &)
|
|
17
|
+
target.send(name, *, &)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def respond_to_missing?(...)
|
|
21
|
+
target.respond_to?(...)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Lazily resolved so application is not referenced at load time.
|
|
27
|
+
def target
|
|
28
|
+
Rails.application.routes.url_helpers
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Utils
|
|
5
|
+
# A helper class for building HTML tables. Used in views.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# <%= table_for @tags do |table| %>
|
|
9
|
+
# <% table.column :name do |tag| %>
|
|
10
|
+
# <%= link_to_wiki "?", tag.name %>
|
|
11
|
+
# <%= link_to tag.name, posts_path(tags: tag.name) %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
# <% table.column :post_count %>
|
|
14
|
+
# <% end %>
|
|
15
|
+
#
|
|
16
|
+
# @see app/views/table_builder/_table.html.erb
|
|
17
|
+
class TableBuilder
|
|
18
|
+
# Represents a single column in the table.
|
|
19
|
+
class Column
|
|
20
|
+
attr_reader(:attribute, :name, :block, :header_attributes, :body_attributes, :caption)
|
|
21
|
+
|
|
22
|
+
# Define a table column.
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# <% table.column :post_count %>
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# <% table.column :name do |tag| %>
|
|
29
|
+
# <%= tag.pretty_name %>
|
|
30
|
+
# <% end %>
|
|
31
|
+
#
|
|
32
|
+
# @param attribute [Symbol] The attribute in the model the column is for.
|
|
33
|
+
# The column's name and value will come from this attribute by default.
|
|
34
|
+
# @param name [String] the column's name, if different from the attribute name.
|
|
35
|
+
# @param column [String] the column name
|
|
36
|
+
# @param th [Hash] the HTML attributes for the column's <th> tag.
|
|
37
|
+
# @param td [Hash] the HTML attributes for the column's <td> tag.
|
|
38
|
+
# @param width [String] the HTML width value for the <th> tag.
|
|
39
|
+
# @yieldparam item a block that returns the column value based on the item.
|
|
40
|
+
def initialize(attribute = nil, column: nil, th: {}, td: {}, width: nil, name: nil, &block)
|
|
41
|
+
@attribute = attribute
|
|
42
|
+
@column = column
|
|
43
|
+
@header_attributes = { width: width, **th }
|
|
44
|
+
@body_attributes = td
|
|
45
|
+
@block = block
|
|
46
|
+
|
|
47
|
+
@name = name || attribute
|
|
48
|
+
@name = @name.to_s.titleize unless @name.is_a?(String)
|
|
49
|
+
|
|
50
|
+
return unless @name.present? || @column.present?
|
|
51
|
+
|
|
52
|
+
if @column.present?
|
|
53
|
+
column_class = "#{@column}-column"
|
|
54
|
+
else
|
|
55
|
+
column_class = "#{@name.parameterize.dasherize}-column"
|
|
56
|
+
end
|
|
57
|
+
@header_attributes[:class] = "#{column_class} #{@header_attributes[:class]}".strip
|
|
58
|
+
@body_attributes[:class] = "#{column_class} #{@body_attributes[:class]}".strip
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns the value of the table cell.
|
|
62
|
+
# @param item [ApplicationRecord] the table cell item
|
|
63
|
+
# @param row [Integer] the table row number
|
|
64
|
+
# @param column [Integer] the table column number
|
|
65
|
+
# @return [#to_s] the value of the table cell
|
|
66
|
+
def value(item, row, column)
|
|
67
|
+
if block.present?
|
|
68
|
+
block.call(item, row, column, self)
|
|
69
|
+
nil
|
|
70
|
+
elsif attribute.is_a?(Symbol)
|
|
71
|
+
item.send(attribute)
|
|
72
|
+
else
|
|
73
|
+
""
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
attr_reader(:columns, :table_attributes, :row_attributes, :items)
|
|
79
|
+
|
|
80
|
+
# Build a table for an array of objects, one object per row.
|
|
81
|
+
#
|
|
82
|
+
# The <table> tag is automatically given an HTML id of the form `{name}-table`.
|
|
83
|
+
# For example, `posts-table`, `tags-table`.
|
|
84
|
+
#
|
|
85
|
+
# The <tr> tag is automatically given an HTML id of the form `{name}-{id}`.
|
|
86
|
+
# For example, `post-1234`, `tag-4567`, etc. Each <tr> tag also gets a set of
|
|
87
|
+
# data attributes for each model; see #html_data_attributes in app/policies.
|
|
88
|
+
#
|
|
89
|
+
# @param items [Array<ApplicationRecord>] The list of ActiveRecord objects to
|
|
90
|
+
# build the table for. One item per table row.
|
|
91
|
+
# @param tr [Hash] optional HTML attributes for the <tr> tag for each row
|
|
92
|
+
# @param table_attributes [Hash] optional HTML attributes for the <table> tag
|
|
93
|
+
# @yieldparam table [self] the table being built
|
|
94
|
+
def initialize(items, tr: {}, **table_attributes)
|
|
95
|
+
@items = items
|
|
96
|
+
@columns = []
|
|
97
|
+
@table_attributes = { class: "striped", **table_attributes }
|
|
98
|
+
@row_attributes = tr
|
|
99
|
+
|
|
100
|
+
@table_attributes[:id] ||= "#{items.model_name.plural.dasherize}-table" if items.respond_to?(:model_name)
|
|
101
|
+
|
|
102
|
+
yield(self) if block_given?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Set the table caption
|
|
106
|
+
def caption
|
|
107
|
+
@caption = yield if block_given?
|
|
108
|
+
@caption
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Add a column to the table.
|
|
112
|
+
# @example
|
|
113
|
+
# table.column(:name)
|
|
114
|
+
def column(*, **options, &)
|
|
115
|
+
opt = options.extract!(:if, :unless)
|
|
116
|
+
return if (opt.key?(:if) && !opt[:if]) || (opt.key?(:unless) && opt[:unless])
|
|
117
|
+
|
|
118
|
+
@columns << Column.new(*, **options, &)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Return the HTML attributes for each <tr> tag.
|
|
122
|
+
# @param item [ApplicationRecord] the item for this row
|
|
123
|
+
# @param _row [Integer] the row number (unused)
|
|
124
|
+
# @return [Hash] the <tr> attributes
|
|
125
|
+
def all_row_attributes(item, _row)
|
|
126
|
+
return {} unless item.is_a?(ApplicationRecord)
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
id: "#{item.model_name.singular.dasherize}-#{item.id}",
|
|
130
|
+
**row_attributes,
|
|
131
|
+
**ApplicationController.helpers.data_attributes_for(item),
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Utils
|
|
5
|
+
module TraceLogger
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
COLORS = {
|
|
9
|
+
black: "\e[30m",
|
|
10
|
+
red: "\e[31m",
|
|
11
|
+
green: "\e[32m",
|
|
12
|
+
yellow: "\e[33m",
|
|
13
|
+
blue: "\e[34m",
|
|
14
|
+
magenta: "\e[35m",
|
|
15
|
+
cyan: "\e[36m",
|
|
16
|
+
white: "\e[37m",
|
|
17
|
+
reset: "\e[0m",
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# noinspection RubyLiteralArrayInspection
|
|
21
|
+
LEVELS = {
|
|
22
|
+
debug: ["%<cyan>s", "%<blue>s"],
|
|
23
|
+
error: ["%<red>s", "%<red>s"],
|
|
24
|
+
info: ["%<cyan>s", "%<blue>s"],
|
|
25
|
+
warn: ["%<yellow>s", "%<yellow>s"],
|
|
26
|
+
default: ["%<white>s", "%<white>s"],
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def format_level(level)
|
|
30
|
+
level = level.to_sym
|
|
31
|
+
primary, alternate = LEVELS.fetch(level, LEVELS[:default])
|
|
32
|
+
colorize("#{alternate}[%<reset>s#{primary}#{level.to_s.upcase}%<reset>s#{alternate}]%<reset>s")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def colorize(text, **)
|
|
36
|
+
format(text, **COLORS, **)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def debug(*, **)
|
|
40
|
+
_log(*, level: :debug, **)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def error(*, **)
|
|
44
|
+
_log(*, level: :error, **)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def info(*, **)
|
|
48
|
+
_log(*, level: :info, **)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def warn(*, **)
|
|
52
|
+
_log(*, level: :warn, **)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def _log(*arg, ignore: nil, level: :log, lines: 3, format: nil)
|
|
56
|
+
return unless Rails.logger.public_send("#{level}?")
|
|
57
|
+
|
|
58
|
+
if arg.one?
|
|
59
|
+
name = nil
|
|
60
|
+
message = arg.first
|
|
61
|
+
else
|
|
62
|
+
name = arg.shift
|
|
63
|
+
message = arg.join
|
|
64
|
+
end
|
|
65
|
+
primary, alternate = LEVELS.fetch(level, LEVELS[:default])
|
|
66
|
+
args = { level: format_level(level), name: name, message: message }
|
|
67
|
+
fmt = "%<level>s"
|
|
68
|
+
if format.nil?
|
|
69
|
+
if name.present?
|
|
70
|
+
fmt += " #{alternate}[%<reset>s%<magenta>s%<name>s%<reset>s#{alternate}]%<reset>s " \
|
|
71
|
+
"#{primary}%<message>s%<reset>s"
|
|
72
|
+
else
|
|
73
|
+
fmt += " #{primary}%<message>s%<reset>s"
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
fmt += " #{format}%<reset>s"
|
|
77
|
+
end
|
|
78
|
+
ignore = Array(ignore).unshift(%r{/yiffspace/utils/trace_logger\.rb})
|
|
79
|
+
callers = caller_locations.reject do |loc|
|
|
80
|
+
path = loc.absolute_path || loc.path
|
|
81
|
+
!Rails.backtrace_cleaner.clean_frame("#{path}:#{loc.lineno}") || ignore.any? { |i| path.match?(i) }
|
|
82
|
+
end
|
|
83
|
+
callers = callers.take(lines) if lines.present?
|
|
84
|
+
Rails.logger.public_send(level, colorize(fmt, **args))
|
|
85
|
+
callers.each { |c| Rails.logger.public_send(level, "↳ #{c.path.gsub(%r{^/app/}, '')}:#{c.lineno} in `#{c.label}`") }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private_class_method(:_log)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|