rails-simple-search 1.2.2 → 2.0.0
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/selection_group.rb +64 -0
- data/lib/selection_item.rb +166 -0
- data/lib/sql_handler.rb +68 -173
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fcf45308630c8fda4c1b7ecce567017b0a77377060aff73ca41600405d2b8a5
|
4
|
+
data.tar.gz: 585040de107d0abf032daf209ad2cd83cfb45ec04ae3276dbb47730060270d2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfb6223b4b906b2d4ef3e748c6c794c62bad9813c1cbfef9b220523826f0e3691874a9426ff2c9bd18fad09cf30b55782d24775efa13f282a187f7a357437247
|
7
|
+
data.tar.gz: 7b6c4c9057c3c0425c362253417932db84ef434771aa43118af9de14530e8ae3a5594e9484e2de3c080c9dc7cc46088ec830fb56775d1800aff6d2e5c971ed0e
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'selection_item'
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
module RailsSimpleSearch
|
5
|
+
module SqlHandler
|
6
|
+
# this class is to represent a sql select statements, union of select
|
7
|
+
# statements, or intersect of select statements
|
8
|
+
class SelectionGroup
|
9
|
+
def self.union_alias_count
|
10
|
+
@union_alias_count ||= 0
|
11
|
+
@union_alias_count += 1
|
12
|
+
@union_alias_count
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(item = nil)
|
16
|
+
@selection_item = item if item
|
17
|
+
@children = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_child(condition_group)
|
21
|
+
raise "It's not allowed to add child into leaf node" if leaf?
|
22
|
+
|
23
|
+
@children << condition_group if condition_group
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_item(selection_item)
|
27
|
+
raise "It's not allowed to add item into non-leaf node" unless empty?
|
28
|
+
|
29
|
+
@selection_item = selection_item
|
30
|
+
end
|
31
|
+
|
32
|
+
def relation(and_or)
|
33
|
+
raise "It's no need to set relation for leaf node" if leaf?
|
34
|
+
|
35
|
+
@relation = and_or
|
36
|
+
end
|
37
|
+
|
38
|
+
def leaf?
|
39
|
+
@selection_item ? true : false
|
40
|
+
end
|
41
|
+
|
42
|
+
def empty?
|
43
|
+
@children.empty? ? true : false
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_sql
|
47
|
+
if leaf?
|
48
|
+
@selection_item.to_sql
|
49
|
+
elsif @relation == :or
|
50
|
+
unioned_sql = @children.map(&:to_sql).join(' union ')
|
51
|
+
"select * from ( #{unioned_sql} ) as #{union_alias}"
|
52
|
+
elsif @relation == :and
|
53
|
+
@children.map(&:to_sql).join(' intersect ')
|
54
|
+
else
|
55
|
+
raise "This should not happen"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def union_alias
|
60
|
+
"union_alias_#{self.class.union_alias_count}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RailsSimpleSearch
|
3
|
+
module SqlHandler
|
4
|
+
# this class represents a single select statement, without unions or intersects.
|
5
|
+
class SelectionItem
|
6
|
+
attr_reader :field, :value
|
7
|
+
|
8
|
+
def initialize(config, model_class, field, value)
|
9
|
+
@config = config
|
10
|
+
@model_class = model_class
|
11
|
+
@field = field
|
12
|
+
@value = value
|
13
|
+
@joins = {}
|
14
|
+
@verb = ''
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_sql
|
18
|
+
build_select_with_condition(@field, @value)
|
19
|
+
join_str = make_joins
|
20
|
+
query = @model_class.joins(join_str)
|
21
|
+
query = query.where(to_ar_condition)
|
22
|
+
query = query.select("distinct #{@model_class.table_name}.*")
|
23
|
+
query.to_sql
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# This method parse a search parameter and its value
|
29
|
+
# then produce a ConditionGroup
|
30
|
+
def build_select_with_condition(attribute, value)
|
31
|
+
# handle direct fields
|
32
|
+
unless attribute =~ /\./
|
33
|
+
condition = build_single_condition(@model_class, @model_class.table_name, attribute, value)
|
34
|
+
return condition
|
35
|
+
end
|
36
|
+
|
37
|
+
# handle association fields
|
38
|
+
association_fields = attribute.split(/\./)
|
39
|
+
field = association_fields.pop
|
40
|
+
|
41
|
+
base_class = @model_class
|
42
|
+
new_asso_chain = true
|
43
|
+
while (current_association_string = association_fields.shift)
|
44
|
+
# polymorphic association with solid target table_name
|
45
|
+
# such as 'commentable:post'
|
46
|
+
if current_association_string.include?(':')
|
47
|
+
poly_asso_name, poly_asso_type = current_association_string.split(':')
|
48
|
+
current_association = base_class.reflect_on_association(poly_asso_name.to_sym)
|
49
|
+
poly_asso_type_class = poly_asso_type.downcase.camelize.constantize
|
50
|
+
insert_join(base_class, current_association, new_asso_chain, poly_asso_type_class)
|
51
|
+
base_class = poly_asso_type_class
|
52
|
+
else
|
53
|
+
current_association = base_class.reflect_on_association(current_association_string.to_sym)
|
54
|
+
insert_join(base_class, current_association, new_asso_chain)
|
55
|
+
base_class = current_association.klass
|
56
|
+
end
|
57
|
+
|
58
|
+
new_asso_chain = false
|
59
|
+
end
|
60
|
+
|
61
|
+
association_alias = table_name_to_alias(base_class.table_name)
|
62
|
+
build_single_condition(base_class, association_alias, field, value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_single_condition(base_class, association_alias, field, value)
|
66
|
+
field, operator = parse_field_name(field)
|
67
|
+
table = base_class.table_name
|
68
|
+
key = "#{table}.#{field}"
|
69
|
+
final_key = "#{association_alias}.#{field}"
|
70
|
+
|
71
|
+
column = base_class.columns_hash[field.to_s]
|
72
|
+
return nil unless column
|
73
|
+
|
74
|
+
if value.nil?
|
75
|
+
verb = 'is'
|
76
|
+
elsif value.is_a?(Array)
|
77
|
+
verb = 'in'
|
78
|
+
elsif operator
|
79
|
+
verb = operator
|
80
|
+
elsif text_column?(column) && ! @config[:exact_match].include?((@model_table_name == table) ? field : key)
|
81
|
+
verb = 'like'
|
82
|
+
value = "%#{value}%"
|
83
|
+
else
|
84
|
+
verb = '='
|
85
|
+
end
|
86
|
+
|
87
|
+
@final_key = final_key
|
88
|
+
@verb = verb
|
89
|
+
@value = value
|
90
|
+
end
|
91
|
+
|
92
|
+
def insert_join(base_class, asso_ref, new_asso_chain, poly_asso_type_class=nil)
|
93
|
+
return if asso_ref.polymorphic? && poly_asso_type_class.blank?
|
94
|
+
base_table = base_class.table_name
|
95
|
+
asso_table = poly_asso_type_class&.table_name || asso_ref.klass.table_name
|
96
|
+
|
97
|
+
@join_count ||= 0
|
98
|
+
return if base_table == asso_table
|
99
|
+
return unless @joins[asso_table].nil?
|
100
|
+
|
101
|
+
@join_count += 1
|
102
|
+
base_table_alias = new_asso_chain ? base_table : table_name_to_alias(base_table)
|
103
|
+
asso_table_alias = format('A%02d', @join_count)
|
104
|
+
|
105
|
+
if asso_ref.belongs_to?
|
106
|
+
if asso_ref.polymorphic?
|
107
|
+
join_cond = "#{base_table_alias}.#{asso_ref.foreign_key} = #{asso_table_alias}.#{poly_asso_type_class.primary_key}"
|
108
|
+
join_cond = "#{base_table_alias}.#{asso_ref.foreign_type} = '#{poly_asso_type_class.name}' and #{join_cond}"
|
109
|
+
else
|
110
|
+
join_cond = "#{base_table_alias}.#{asso_ref.foreign_key} = #{asso_table_alias}.#{asso_ref.klass.primary_key}"
|
111
|
+
end
|
112
|
+
else
|
113
|
+
join_cond = "#{base_table_alias}.#{base_class.primary_key} = #{asso_table_alias}.#{asso_ref.foreign_key}"
|
114
|
+
join_cond = "#{asso_table_alias}.#{asso_ref.type} = '#{base_class.name}' and #{join_cond}" if asso_ref.type
|
115
|
+
end
|
116
|
+
@joins[asso_table] = [@join_count, asso_table, join_cond]
|
117
|
+
end
|
118
|
+
|
119
|
+
def table_name_to_alias(table_name)
|
120
|
+
format('A%02d', @joins[table_name][0])
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_field_name(name)
|
124
|
+
if name =~ /^(.*)?((_(greater|less)_than)(_equal_to)?)$/
|
125
|
+
field_name = ::Regexp.last_match(1)
|
126
|
+
operator = (::Regexp.last_match(4) == 'greater' ? '>' : '<')
|
127
|
+
operator << '=' if ::Regexp.last_match(5)
|
128
|
+
else
|
129
|
+
field_name = name
|
130
|
+
end
|
131
|
+
|
132
|
+
[field_name, operator]
|
133
|
+
end
|
134
|
+
|
135
|
+
def text_column?(column)
|
136
|
+
if column.respond_to?(:text?)
|
137
|
+
column.text?
|
138
|
+
elsif column.respond_to?(:type)
|
139
|
+
column.type == :string || column.type == :text
|
140
|
+
else
|
141
|
+
raise 'encountered new version of Rails'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def make_joins
|
146
|
+
joins_str = ''
|
147
|
+
joins = @joins.values
|
148
|
+
joins.sort! { |a, b| a[0] <=> b[0] }
|
149
|
+
joins.each do |j|
|
150
|
+
table = j[1]
|
151
|
+
constrain = j[2]
|
152
|
+
joins_str += format(" inner join #{table} AS A%02d on #{constrain}", j[0])
|
153
|
+
end
|
154
|
+
joins_str
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_ar_condition
|
158
|
+
condition = []
|
159
|
+
condition << "#{@final_key} #{@verb}"
|
160
|
+
condition[0] << (@verb == 'in' ? ' (?)' : ' ?')
|
161
|
+
condition << @value
|
162
|
+
condition
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/sql_handler.rb
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
require_relative 'selection_group.rb'
|
2
|
+
require_relative 'selection_item.rb'
|
3
|
+
|
4
|
+
# This gem returns some rows of a certain table (model), according to the search parameters.
|
5
|
+
# Each search parameter can ba a direct field (like first_name, last_name), indirect
|
6
|
+
# field (like address.city, posts.comments.author.first_nane), or composite field
|
7
|
+
# (like, address.city_or_posts.comments.author.city).
|
8
|
+
#
|
9
|
+
# For example, if we have 3 search parameters, {"serach":{"aaa":"aaa"}, {"bbb.ccc":"ccc"}, {"ddd.eee_or_ggg.hhh":"hhh"}}
|
10
|
+
# The psudo result sql statement would look like:
|
11
|
+
#
|
12
|
+
# select * from base_model
|
13
|
+
# where aaa = 'aaa'
|
14
|
+
#
|
15
|
+
# intersect
|
16
|
+
#
|
17
|
+
# select * from base_model
|
18
|
+
# join xxxxxx
|
19
|
+
# where ccc = 'ccc'
|
20
|
+
#
|
21
|
+
# intersect
|
22
|
+
#
|
23
|
+
# select * from
|
24
|
+
# (
|
25
|
+
# select * from base_model
|
26
|
+
# join xxxxxx
|
27
|
+
# where eee = 'ccc'
|
28
|
+
#
|
29
|
+
# union
|
30
|
+
#
|
31
|
+
# select * from base_model
|
32
|
+
# join xxxxxx
|
33
|
+
# where hhh= 'ccc'
|
34
|
+
# ) as union_result_1
|
35
|
+
#
|
1
36
|
module RailsSimpleSearch
|
2
37
|
module SqlHandler
|
3
38
|
def init
|
@@ -10,196 +45,56 @@ module RailsSimpleSearch
|
|
10
45
|
instance_eval(&pre_processor)
|
11
46
|
end
|
12
47
|
|
13
|
-
|
48
|
+
selection_group = generate_selection_group(@criteria)
|
14
49
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def text_column?(column)
|
23
|
-
if column.respond_to?(:text?)
|
24
|
-
column.text?
|
25
|
-
elsif column.respond_to?(:type)
|
26
|
-
column.type == :string || column.type == :text
|
50
|
+
raw_sql = selection_group.to_sql
|
51
|
+
if raw_sql.blank?
|
52
|
+
@model_class.all
|
27
53
|
else
|
28
|
-
|
54
|
+
@model_class.from("(#{raw_sql}) AS #{@model_table_name}")
|
29
55
|
end
|
30
56
|
end
|
31
57
|
|
32
|
-
|
33
|
-
@joins_str = ''
|
34
|
-
joins = @joins.values
|
35
|
-
joins.sort! { |a, b| a[0] <=> b[0] }
|
36
|
-
joins.each do |j|
|
37
|
-
table = j[1]
|
38
|
-
constrain = j[2]
|
39
|
-
@joins_str << format(" inner join #{table} AS A%02d on #{constrain}", j[0])
|
40
|
-
end
|
41
|
-
end
|
58
|
+
private
|
42
59
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
60
|
+
def generate_selection_group(criteria)
|
61
|
+
selection_group = SelectionGroup.new
|
62
|
+
selection_group.relation(:and)
|
63
|
+
|
64
|
+
criteria.each do |key, value|
|
65
|
+
sg = SelectionGroup.new
|
66
|
+
fields = key.split(@config[:or_separator])
|
67
|
+
if fields.size > 1
|
68
|
+
# if the key is "or"ed by muiltiple fields
|
69
|
+
# we generate a SelectionGroup to include all the fields
|
70
|
+
sg.relation(:or)
|
71
|
+
fields.each do |f|
|
72
|
+
si = SelectionItem.new(@config, @model_class, f, value)
|
73
|
+
sg_si = SelectionGroup.new(si)
|
74
|
+
sg.add_child(sg_si)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
sg.add_item(SelectionItem.new(@config, @model_class, fields.first, value))
|
78
|
+
end
|
46
79
|
|
47
|
-
|
48
|
-
@condition_group.add(parse_search_parameters(key, value))
|
80
|
+
selection_group.add_child(sg)
|
49
81
|
end
|
50
82
|
|
51
|
-
|
83
|
+
selection_group
|
52
84
|
end
|
53
85
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
column = base_class.columns_hash[field.to_s]
|
61
|
-
return nil unless column
|
62
|
-
|
63
|
-
if value.nil?
|
64
|
-
verb = 'is'
|
65
|
-
elsif value.is_a?(Array)
|
66
|
-
verb = 'in'
|
67
|
-
elsif operator
|
68
|
-
verb = operator
|
69
|
-
elsif text_column?(column) && ! @config[:exact_match].include?((@model_table_name == table) ? field : key)
|
70
|
-
verb = 'like'
|
71
|
-
value = "%#{value}%"
|
86
|
+
def text_column?(column)
|
87
|
+
if column.respond_to?(:text?)
|
88
|
+
column.text?
|
89
|
+
elsif column.respond_to?(:type)
|
90
|
+
column.type == :string || column.type == :text
|
72
91
|
else
|
73
|
-
|
92
|
+
raise 'encountered new version of Rails'
|
74
93
|
end
|
75
|
-
|
76
|
-
ConditionGroup.new(ConditionItem.new(final_key, verb, value))
|
77
94
|
end
|
78
95
|
|
79
96
|
def table_name_to_alias(table_name)
|
80
97
|
format('A%02d', @joins[table_name][0])
|
81
98
|
end
|
82
|
-
|
83
|
-
def insert_join(base_class, asso_ref, new_asso_chain)
|
84
|
-
base_table = base_class.table_name
|
85
|
-
asso_table = asso_ref.klass.table_name
|
86
|
-
|
87
|
-
@join_count ||= 0
|
88
|
-
return if base_table == asso_table
|
89
|
-
return unless @joins[asso_table].nil?
|
90
|
-
|
91
|
-
@join_count += 1
|
92
|
-
base_table_alias = new_asso_chain ? base_table : table_name_to_alias(base_table)
|
93
|
-
asso_table_alias = format('A%02d', @join_count)
|
94
|
-
|
95
|
-
if asso_ref.belongs_to?
|
96
|
-
@joins[asso_table] =[@join_count, asso_table, "#{base_table_alias}.#{asso_ref.foreign_key} = #{asso_table_alias}.#{asso_ref.klass.primary_key}"]
|
97
|
-
else
|
98
|
-
join_cond = "#{base_table_alias}.#{base_class.primary_key} = #{asso_table_alias}.#{asso_ref.foreign_key}"
|
99
|
-
join_cond = "#{asso_table_alias}.#{asso_ref.type} = '#{base_class.name}' and #{join_cond}" if asso_ref.type
|
100
|
-
@joins[asso_table] = [@join_count, asso_table, join_cond]
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# This method parse a search parameter and its value
|
105
|
-
# then produce a ConditionGroup
|
106
|
-
def parse_search_parameters(attribute, value)
|
107
|
-
# handle _or_ parameters
|
108
|
-
attributes = attribute.split(@config[:or_separator])
|
109
|
-
if attributes.size > 1
|
110
|
-
cg = ConditionGroup.new
|
111
|
-
cg.set_relation(:or)
|
112
|
-
attributes.each do |a|
|
113
|
-
cg.add(parse_search_parameters(a, value))
|
114
|
-
end
|
115
|
-
return cg
|
116
|
-
end
|
117
|
-
|
118
|
-
# handle direct fields
|
119
|
-
unless attribute =~ /\./
|
120
|
-
condition = build_single_condition(@model_class, @model_class.table_name, attribute, value)
|
121
|
-
return condition
|
122
|
-
end
|
123
|
-
|
124
|
-
# handle association fields
|
125
|
-
association_fields = attribute.split(/\./)
|
126
|
-
field = association_fields.pop
|
127
|
-
|
128
|
-
base_class = @model_class
|
129
|
-
new_asso_chain = true
|
130
|
-
until association_fields.empty?
|
131
|
-
association_fields[0] = base_class.reflect_on_association(association_fields[0].to_sym)
|
132
|
-
insert_join(base_class, association_fields[0], new_asso_chain)
|
133
|
-
new_asso_chain = false
|
134
|
-
base_class = association_fields.shift.klass
|
135
|
-
end
|
136
|
-
|
137
|
-
association_alias = table_name_to_alias(base_class.table_name)
|
138
|
-
build_single_condition(base_class, association_alias, field, value)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# This class holds a single condition
|
143
|
-
class ConditionItem
|
144
|
-
attr_reader :field, :verb, :value
|
145
|
-
|
146
|
-
def initialize(field, verb, value)
|
147
|
-
@field = field
|
148
|
-
@verb = verb
|
149
|
-
@value = value
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# This class holds a ConditionGroup
|
154
|
-
# One ConditionGroup can hold one or more
|
155
|
-
# conditions
|
156
|
-
class ConditionGroup
|
157
|
-
def initialize(item = nil)
|
158
|
-
if item
|
159
|
-
@condition_item = item
|
160
|
-
else
|
161
|
-
@children = []
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def add(condition_group)
|
166
|
-
raise "It's not allowed to add child into leaf node" if leaf?
|
167
|
-
|
168
|
-
@children << condition_group if condition_group
|
169
|
-
end
|
170
|
-
|
171
|
-
def set_relation(and_or)
|
172
|
-
raise "It's not needed to set relation for leaf node" if leaf?
|
173
|
-
|
174
|
-
@relation = and_or
|
175
|
-
end
|
176
|
-
|
177
|
-
def leaf?
|
178
|
-
@condition_item ? true : false
|
179
|
-
end
|
180
|
-
|
181
|
-
def empty?
|
182
|
-
@children && @children.empty? ? true : false
|
183
|
-
end
|
184
|
-
|
185
|
-
def to_ar_condition
|
186
|
-
condition = []
|
187
|
-
if leaf?
|
188
|
-
i = @condition_item
|
189
|
-
condition << "#{i.field} #{i.verb}"
|
190
|
-
condition[0] << (i.verb == 'in' ? ' (?)' : ' ?')
|
191
|
-
condition << i.value
|
192
|
-
else
|
193
|
-
tmp_conditions = @children.map(&:to_ar_condition)
|
194
|
-
tmp_condition_str = tmp_conditions.map(&:first).join(" #{@relation} ")
|
195
|
-
condition << "(#{tmp_condition_str})"
|
196
|
-
tmp_conditions.each do |t|
|
197
|
-
(1..(t.length - 1)).each do |index|
|
198
|
-
condition << t[index]
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
condition
|
203
|
-
end
|
204
99
|
end
|
205
100
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-simple-search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yi Zhang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: rails-simple-search is a light and easy to use gem. It could help developers
|
14
14
|
quickly build a search page.
|
@@ -19,6 +19,8 @@ extra_rdoc_files: []
|
|
19
19
|
files:
|
20
20
|
- README.md
|
21
21
|
- lib/rails-simple-search.rb
|
22
|
+
- lib/selection_group.rb
|
23
|
+
- lib/selection_item.rb
|
22
24
|
- lib/sql_handler.rb
|
23
25
|
homepage: http://github.com/yzhanginwa/rails-simple-search
|
24
26
|
licenses:
|
@@ -39,7 +41,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
41
|
- !ruby/object:Gem::Version
|
40
42
|
version: '0'
|
41
43
|
requirements: []
|
42
|
-
rubygems_version: 3.
|
44
|
+
rubygems_version: 3.5.11
|
43
45
|
signing_key:
|
44
46
|
specification_version: 4
|
45
47
|
summary: A very simple and light gem to quickly build search/filter function in rails
|