kasket 4.4.3 → 4.4.4
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/kasket.rb +2 -1
- data/lib/kasket/configuration_mixin.rb +18 -14
- data/lib/kasket/dirty_mixin.rb +2 -1
- data/lib/kasket/query_parser.rb +29 -24
- data/lib/kasket/read_mixin.rb +10 -10
- data/lib/kasket/relation_mixin.rb +6 -5
- data/lib/kasket/select_manager_mixin.rb +2 -1
- data/lib/kasket/version.rb +2 -1
- data/lib/kasket/visitor.rb +32 -25
- data/lib/kasket/write_mixin.rb +17 -10
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e93590bf7fe78f25a1d988db077414a374e0a93
|
4
|
+
data.tar.gz: cd462545f88c3a6e1721b9be75fee8e1c6ec6905
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de430ca6f3a4ee7c0846c7f398cd01592dd7f8dad5972822f9d201f075a11d7c4b6648ab5a2c54afdf2dd5de2a7b4370462f1b7bf78eb855cdb7528e9698f15a
|
7
|
+
data.tar.gz: bb2894c5baad7af7865cb206c7293eee9534d5acd4a20022f360c125aeb55d58fcf630c05828090d6b9c31bb0891f878003f212e5f609055dd0b727329c6b6c9
|
data/lib/kasket.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_record'
|
2
3
|
require 'active_support'
|
3
4
|
|
@@ -14,7 +15,7 @@ module Kasket
|
|
14
15
|
autoload :SelectManagerMixin, 'kasket/select_manager_mixin'
|
15
16
|
autoload :RelationMixin, 'kasket/relation_mixin'
|
16
17
|
|
17
|
-
CONFIGURATION = {:
|
18
|
+
CONFIGURATION = { max_collection_size: 100, write_through: false } # rubocop:disable Style/MutableConstant
|
18
19
|
|
19
20
|
module_function
|
20
21
|
|
@@ -1,11 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_support'
|
2
3
|
require "digest/md5"
|
3
4
|
|
4
5
|
module Kasket
|
5
|
-
|
6
6
|
module ConfigurationMixin
|
7
|
-
|
8
|
-
def without_kasket(&block)
|
7
|
+
def without_kasket
|
9
8
|
old_value = Thread.current[:kasket_disabled] || false
|
10
9
|
Thread.current[:kasket_disabled] = true
|
11
10
|
yield
|
@@ -22,7 +21,10 @@ module Kasket
|
|
22
21
|
end
|
23
22
|
|
24
23
|
def kasket_key_prefix
|
25
|
-
@kasket_key_prefix ||=
|
24
|
+
@kasket_key_prefix ||= begin
|
25
|
+
column_hash = column_names.join.sum
|
26
|
+
"kasket-#{Kasket::Version::PROTOCOL}/#{kasket_activerecord_version}/#{table_name}/version=#{column_hash}/"
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def kasket_activerecord_version
|
@@ -36,7 +38,7 @@ module Kasket
|
|
36
38
|
key = attribute_value_pairs.map do |attribute, value|
|
37
39
|
column = columns_hash[attribute.to_s]
|
38
40
|
value = nil if value.blank?
|
39
|
-
attribute
|
41
|
+
"#{attribute}=#{quoted_value_for_column(value, column)}"
|
40
42
|
end.join('/')
|
41
43
|
|
42
44
|
if key.size > (250 - kasket_key_prefix.size) || key =~ /\s/
|
@@ -65,12 +67,14 @@ module Kasket
|
|
65
67
|
kasket_indices.include?(sorted_attributes)
|
66
68
|
end
|
67
69
|
|
68
|
-
def has_kasket
|
70
|
+
def has_kasket
|
69
71
|
has_kasket_on :id
|
70
72
|
end
|
71
73
|
|
72
74
|
def has_kasket_on(*args)
|
73
75
|
attributes = args.sort_by!(&:to_s)
|
76
|
+
|
77
|
+
# enforce id index
|
74
78
|
if attributes != [:id] && !has_kasket_index_on?([:id])
|
75
79
|
has_kasket_on(:id)
|
76
80
|
end
|
@@ -93,14 +97,14 @@ module Kasket
|
|
93
97
|
|
94
98
|
def quoted_value_for_column(value, column)
|
95
99
|
if column
|
96
|
-
casted_value =
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
casted_value =
|
101
|
+
if connection.respond_to?(:type_cast_from_column)
|
102
|
+
connection.type_cast_from_column(column, value)
|
103
|
+
elsif column.respond_to?(:type_cast_for_database)
|
104
|
+
column.type_cast_for_database(value) # Rails 4.2
|
105
|
+
else
|
106
|
+
column.type_cast(value)
|
107
|
+
end
|
104
108
|
connection.quote(casted_value).downcase
|
105
109
|
else
|
106
110
|
value.to_s
|
data/lib/kasket/dirty_mixin.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Kasket
|
2
3
|
module DirtyMixin
|
3
4
|
def kasket_dirty_methods(*method_names)
|
4
5
|
method_names.each do |method|
|
5
6
|
without = "without_kasket_update_#{method}"
|
6
|
-
|
7
|
+
break if method_defined? without
|
7
8
|
|
8
9
|
alias_method without, method
|
9
10
|
define_method method do |*args, &block|
|
data/lib/kasket/query_parser.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Kasket
|
2
3
|
class QueryParser
|
3
4
|
# Examples:
|
@@ -10,16 +11,21 @@ module Kasket
|
|
10
11
|
|
11
12
|
def initialize(model_class)
|
12
13
|
@model_class = model_class
|
14
|
+
|
13
15
|
@supported_query_pattern = /^select\s+((?:`|")#{@model_class.table_name}(?:`|")\.)?\* from (?:`|")#{@model_class.table_name}(?:`|") where (.*?)(|\s+limit 1)\s*$/i
|
14
|
-
|
15
|
-
|
16
|
+
|
17
|
+
# Matches: `users`.id, `users`.`id`, users.id, id
|
18
|
+
@table_and_column_pattern = /(?:(?:`|")?#{@model_class.table_name}(?:`|")?\.)?(?:`|")?([a-zA-Z]\w*)(?:`|")?/
|
19
|
+
# Matches: KEY = VALUE, (KEY = VALUE), ()(KEY = VALUE))
|
20
|
+
@key_eq_value_pattern = /^[\(\s]*#{@table_and_column_pattern}\s+(=|IN)\s+#{VALUE}[\)\s]*$/
|
16
21
|
end
|
17
22
|
|
18
23
|
def parse(sql)
|
19
24
|
if match = @supported_query_pattern.match(sql)
|
20
|
-
where
|
25
|
+
where = match[2]
|
26
|
+
limit = match[3]
|
21
27
|
|
22
|
-
query =
|
28
|
+
query = {}
|
23
29
|
query[:attributes] = sorted_attribute_value_pairs(where)
|
24
30
|
return nil if query[:attributes].nil?
|
25
31
|
|
@@ -38,33 +44,32 @@ module Kasket
|
|
38
44
|
|
39
45
|
private
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
47
|
+
def sorted_attribute_value_pairs(conditions)
|
48
|
+
if attributes = parse_condition(conditions)
|
49
|
+
attributes.sort { |pair1, pair2| pair1[0].to_s <=> pair2[0].to_s }
|
45
50
|
end
|
51
|
+
end
|
46
52
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
else
|
57
|
-
return nil
|
58
|
-
end
|
53
|
+
def parse_condition(conditions = '', *values)
|
54
|
+
values = values.dup
|
55
|
+
conditions.split(AND).inject([]) do |pairs, condition|
|
56
|
+
matched, column_name, operator, sql_value = *@key_eq_value_pattern.match(condition)
|
57
|
+
if matched
|
58
|
+
if operator == 'IN'
|
59
|
+
if column_name == 'id'
|
60
|
+
values = sql_value[1..-2].split(',').map(&:strip)
|
61
|
+
pairs << [column_name.to_sym, values]
|
59
62
|
else
|
60
|
-
|
61
|
-
pairs << [column_name.to_sym, value.gsub(/''|\\'/, "'")]
|
63
|
+
return nil
|
62
64
|
end
|
63
65
|
else
|
64
|
-
|
66
|
+
value = sql_value == '?' ? values.shift : sql_value
|
67
|
+
pairs << [column_name.to_sym, value.gsub(/''|\\'/, "'")]
|
65
68
|
end
|
69
|
+
else
|
70
|
+
return nil
|
66
71
|
end
|
67
72
|
end
|
68
|
-
|
73
|
+
end
|
69
74
|
end
|
70
75
|
end
|
data/lib/kasket/read_mixin.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Kasket
|
2
3
|
module ReadMixin
|
3
|
-
|
4
4
|
def self.extended(base)
|
5
5
|
class << base
|
6
6
|
alias_method :find_by_sql_without_kasket, :find_by_sql
|
@@ -12,14 +12,14 @@ module Kasket
|
|
12
12
|
sql = args[0]
|
13
13
|
|
14
14
|
if use_kasket?
|
15
|
-
if sql.respond_to?(:to_kasket_query)
|
15
|
+
query = if sql.respond_to?(:to_kasket_query)
|
16
16
|
if ActiveRecord::VERSION::MAJOR < 5
|
17
|
-
|
17
|
+
sql.to_kasket_query(self, args[1])
|
18
18
|
else
|
19
|
-
|
19
|
+
sql.to_kasket_query(self, args[1].map(&:value_for_database))
|
20
20
|
end
|
21
21
|
else
|
22
|
-
|
22
|
+
kasket_parser.parse(sanitize_sql(sql))
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -45,8 +45,8 @@ module Kasket
|
|
45
45
|
def find_by_sql_with_kasket_on_id_array(keys)
|
46
46
|
key_attributes_map = Kasket.cache.read_multi(*keys)
|
47
47
|
|
48
|
-
found_keys, missing_keys = keys.partition{|k| key_attributes_map[k] }
|
49
|
-
found_keys.each{|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup) }
|
48
|
+
found_keys, missing_keys = keys.partition {|k| key_attributes_map[k] }
|
49
|
+
found_keys.each {|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup) }
|
50
50
|
key_attributes_map.merge!(missing_records_from_db(missing_keys))
|
51
51
|
|
52
52
|
key_attributes_map.values.compact
|
@@ -65,11 +65,11 @@ module Kasket
|
|
65
65
|
def missing_records_from_db(missing_keys)
|
66
66
|
return {} if missing_keys.empty?
|
67
67
|
|
68
|
-
id_key_map = Hash[missing_keys.map{|key| [key.split('=').last.to_i, key] }]
|
68
|
+
id_key_map = Hash[missing_keys.map {|key| [key.split('=').last.to_i, key] }]
|
69
69
|
|
70
|
-
found = without_kasket { where(:
|
70
|
+
found = without_kasket { where(id: id_key_map.keys).to_a }
|
71
71
|
found.each(&:store_in_kasket)
|
72
|
-
Hash[found.map{|record| [id_key_map[record.id], record] }]
|
72
|
+
Hash[found.map {|record| [id_key_map[record.id], record] }]
|
73
73
|
end
|
74
74
|
|
75
75
|
def store_in_kasket(key, records)
|
@@ -1,12 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Kasket
|
2
3
|
module RelationMixin
|
3
4
|
def to_kasket_query(binds = nil)
|
4
5
|
if arel.is_a?(Arel::SelectManager)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
if ActiveRecord::VERSION::MAJOR < 5
|
7
|
+
arel.to_kasket_query(klass, (binds || bind_values))
|
8
|
+
else
|
9
|
+
arel.to_kasket_query(klass, (@values[:where].binds.map(&:value_for_database) + Array(@values[:limit])))
|
10
|
+
end
|
10
11
|
end
|
11
12
|
rescue TypeError # unsupported object in ast
|
12
13
|
return nil
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Kasket
|
2
3
|
module SelectManagerMixin
|
3
4
|
def to_kasket_query(klass, binds = [])
|
@@ -17,7 +18,7 @@ module Kasket
|
|
17
18
|
# return nil if !query[:index].include?(:id)
|
18
19
|
end
|
19
20
|
|
20
|
-
if query[:index].size > 1 && query[:attributes].any? { |
|
21
|
+
if query[:index].size > 1 && query[:attributes].any? { |_attribute, value| value.is_a?(Array) }
|
21
22
|
return nil
|
22
23
|
end
|
23
24
|
|
data/lib/kasket/version.rb
CHANGED
data/lib/kasket/visitor.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'arel'
|
2
3
|
|
3
4
|
module Kasket
|
@@ -13,6 +14,8 @@ module Kasket
|
|
13
14
|
super
|
14
15
|
end
|
15
16
|
|
17
|
+
private
|
18
|
+
|
16
19
|
def last_column=(col)
|
17
20
|
Thread.current[:arel_visitors_to_sql_last_column] = col
|
18
21
|
end
|
@@ -29,7 +32,7 @@ module Kasket
|
|
29
32
|
return :unsupported if node.with
|
30
33
|
return :unsupported if node.offset
|
31
34
|
return :unsupported if node.lock
|
32
|
-
return :unsupported if node
|
35
|
+
return :unsupported if ordered?(node)
|
33
36
|
return :unsupported if node.cores.size != 1
|
34
37
|
|
35
38
|
query = visit_Arel_Nodes_SelectCore(node.cores[0])
|
@@ -45,9 +48,9 @@ module Kasket
|
|
45
48
|
|
46
49
|
def visit_Arel_Nodes_SelectCore(node, *_)
|
47
50
|
return :unsupported if node.groups.any?
|
48
|
-
return :unsupported if
|
51
|
+
return :unsupported if ActiveRecord::VERSION::MAJOR < 5 ? node.having : node.havings.present?
|
49
52
|
return :unsupported if node.set_quantifier
|
50
|
-
return :unsupported if
|
53
|
+
return :unsupported if !node.source || node.source.empty?
|
51
54
|
return :unsupported if node.projections.size != 1
|
52
55
|
|
53
56
|
select = node.projections[0]
|
@@ -62,28 +65,28 @@ module Kasket
|
|
62
65
|
end
|
63
66
|
|
64
67
|
def visit_Arel_Nodes_Limit(node, *_)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
if ActiveRecord::VERSION::MAJOR < 5
|
69
|
+
{ limit: node.value.to_i }
|
70
|
+
else
|
71
|
+
{ limit: visit(node.value).to_i }
|
72
|
+
end
|
70
73
|
end
|
71
74
|
|
72
75
|
def visit_Arel_Nodes_JoinSource(node, *_)
|
73
76
|
return :unsupported if !node.left || node.right.any?
|
74
|
-
return :unsupported
|
77
|
+
return :unsupported unless node.left.is_a?(Arel::Table)
|
75
78
|
visit(node.left)
|
76
79
|
end
|
77
80
|
|
78
81
|
def visit_Arel_Table(node, *_)
|
79
|
-
{:
|
82
|
+
{ from: node.name }
|
80
83
|
end
|
81
84
|
|
82
85
|
def visit_Arel_Nodes_And(node, *_)
|
83
86
|
attributes = node.children.map { |child| visit(child) }
|
84
87
|
return :unsupported if attributes.include?(:unsupported)
|
85
88
|
attributes.sort! { |pair1, pair2| pair1[0].to_s <=> pair2[0].to_s }
|
86
|
-
{ :
|
89
|
+
{ attributes: attributes }
|
87
90
|
end
|
88
91
|
|
89
92
|
def visit_Arel_Nodes_In(node, *_)
|
@@ -94,12 +97,12 @@ module Kasket
|
|
94
97
|
end
|
95
98
|
|
96
99
|
def visit_Arel_Nodes_Equality(node, *_)
|
97
|
-
right =
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
visit(node.right)
|
102
|
-
|
100
|
+
right =
|
101
|
+
case node.right
|
102
|
+
when false then 0 # This should probably be removed when Rails 3.2 is not supported anymore
|
103
|
+
when nil then nil
|
104
|
+
else visit(node.right)
|
105
|
+
end
|
103
106
|
[visit(node.left), right]
|
104
107
|
end
|
105
108
|
|
@@ -110,14 +113,13 @@ module Kasket
|
|
110
113
|
|
111
114
|
def literal(node, *_)
|
112
115
|
if node == '?'
|
113
|
-
|
114
|
-
value.to_s
|
116
|
+
@binds.shift.last.to_s
|
115
117
|
else
|
116
118
|
node.to_s
|
117
119
|
end
|
118
120
|
end
|
119
121
|
|
120
|
-
def visit_Arel_Nodes_BindParam(
|
122
|
+
def visit_Arel_Nodes_BindParam(_x, *_)
|
121
123
|
if ActiveRecord::VERSION::MAJOR < 5
|
122
124
|
visit(@binds.shift[1])
|
123
125
|
else
|
@@ -133,11 +135,11 @@ module Kasket
|
|
133
135
|
quoted(node.val) unless node.val.nil?
|
134
136
|
end
|
135
137
|
|
136
|
-
def visit_TrueClass(
|
138
|
+
def visit_TrueClass(_node)
|
137
139
|
1
|
138
140
|
end
|
139
141
|
|
140
|
-
def visit_FalseClass(
|
142
|
+
def visit_FalseClass(_node)
|
141
143
|
0
|
142
144
|
end
|
143
145
|
|
@@ -145,8 +147,13 @@ module Kasket
|
|
145
147
|
@model_class.connection.quote(node)
|
146
148
|
end
|
147
149
|
|
148
|
-
|
149
|
-
|
150
|
-
|
150
|
+
# any non `id asc` ordering
|
151
|
+
def ordered?(node)
|
152
|
+
!node.orders.all? { |o| o.is_a?(Arel::Nodes::Ascending) && o.expr.name == "id" }
|
153
|
+
end
|
154
|
+
|
155
|
+
alias_method :visit_String, :literal
|
156
|
+
alias_method :visit_Fixnum, :literal
|
157
|
+
alias_method :visit_Arel_Nodes_SqlLiteral, :literal
|
151
158
|
end
|
152
159
|
end
|
data/lib/kasket/write_mixin.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Kasket
|
2
3
|
module WriteMixin
|
3
|
-
|
4
4
|
module ClassMethods
|
5
5
|
def remove_from_kasket(ids)
|
6
6
|
Array(ids).each do |id|
|
@@ -38,20 +38,25 @@ module Kasket
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def kasket_keys(options = {})
|
41
|
-
attribute_sets = [attributes
|
41
|
+
attribute_sets = [attributes]
|
42
42
|
|
43
43
|
previous_changes = options[:previous_changes] || previous_changes()
|
44
44
|
if previous_changes.present?
|
45
|
-
old_attributes =
|
45
|
+
old_attributes = previous_changes.each_with_object({}) do |(attribute, (old, _)), all|
|
46
|
+
all[attribute.to_s] = old
|
47
|
+
end
|
46
48
|
attribute_sets << old_attributes.reverse_merge(attribute_sets[0])
|
49
|
+
attribute_sets << attribute_sets[0].merge(old_attributes)
|
47
50
|
end
|
48
51
|
|
49
52
|
keys = []
|
50
53
|
self.class.kasket_indices.each do |index|
|
51
|
-
keys
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
keys.concat(
|
55
|
+
attribute_sets.map do |attribute_set|
|
56
|
+
key = self.class.kasket_key_for(index.map { |attribute| [attribute, attribute_set[attribute.to_s]] })
|
57
|
+
index.include?(:id) ? key : [key, key + '/first']
|
58
|
+
end
|
59
|
+
)
|
55
60
|
end
|
56
61
|
|
57
62
|
keys.flatten!
|
@@ -85,7 +90,7 @@ module Kasket
|
|
85
90
|
|
86
91
|
if ActiveRecord::VERSION::MAJOR == 3
|
87
92
|
def update_column(column, value)
|
88
|
-
previous_changes = {column => [attributes[column.to_s], value]}
|
93
|
+
previous_changes = { column => [attributes[column.to_s], value] }
|
89
94
|
result = super
|
90
95
|
clear_kasket_indices(previous_changes: previous_changes)
|
91
96
|
result
|
@@ -93,7 +98,9 @@ module Kasket
|
|
93
98
|
else
|
94
99
|
def update_columns(new_attributes)
|
95
100
|
previous_attributes = attributes
|
96
|
-
previous_changes = new_attributes.each_with_object({})
|
101
|
+
previous_changes = new_attributes.each_with_object({}) do |(k, v), all|
|
102
|
+
all[k] = [previous_attributes[k.to_s], v]
|
103
|
+
end
|
97
104
|
result = super
|
98
105
|
clear_kasket_indices(previous_changes: previous_changes)
|
99
106
|
result
|
@@ -109,7 +116,7 @@ module Kasket
|
|
109
116
|
end
|
110
117
|
|
111
118
|
def kasket_after_destroy
|
112
|
-
Kasket.add_pending_record(self,
|
119
|
+
Kasket.add_pending_record(self, _destroyed = true)
|
113
120
|
end
|
114
121
|
|
115
122
|
def committed!(*)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kasket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.4.
|
4
|
+
version: 4.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mick Staugaard
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-08-
|
12
|
+
date: 2017-08-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -173,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
173
173
|
requirements:
|
174
174
|
- - ">="
|
175
175
|
- !ruby/object:Gem::Version
|
176
|
-
version:
|
176
|
+
version: 2.2.0
|
177
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
178
|
requirements:
|
179
179
|
- - ">="
|