rails-simple-search 1.3.0 → 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 -190
- 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,213 +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
|
27
|
-
else
|
28
|
-
raise 'encountered new version of Rails'
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def make_joins
|
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
|
42
|
-
|
43
|
-
def run_criteria
|
44
|
-
@condition_group = ConditionGroup.new
|
45
|
-
@condition_group.set_relation(:and)
|
46
|
-
|
47
|
-
@criteria.each do |key, value|
|
48
|
-
@condition_group.add(parse_search_parameters(key, value))
|
49
|
-
end
|
50
|
-
|
51
|
-
make_joins
|
52
|
-
end
|
53
|
-
|
54
|
-
def build_single_condition(base_class, association_alias, field, value)
|
55
|
-
field, operator = parse_field_name(field)
|
56
|
-
table = base_class.table_name
|
57
|
-
key = "#{table}.#{field}"
|
58
|
-
final_key = "#{association_alias}.#{field}"
|
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}%"
|
72
|
-
else
|
73
|
-
verb = '='
|
74
|
-
end
|
75
|
-
|
76
|
-
ConditionGroup.new(ConditionItem.new(final_key, verb, value))
|
77
|
-
end
|
78
|
-
|
79
|
-
def table_name_to_alias(table_name)
|
80
|
-
format('A%02d', @joins[table_name][0])
|
81
|
-
end
|
82
|
-
|
83
|
-
def insert_join(base_class, asso_ref, new_asso_chain, poly_asso_type_class=nil)
|
84
|
-
return if asso_ref.polymorphic? && poly_asso_type_class.blank?
|
85
|
-
base_table = base_class.table_name
|
86
|
-
asso_table = poly_asso_type_class&.table_name || asso_ref.klass.table_name
|
87
|
-
|
88
|
-
@join_count ||= 0
|
89
|
-
return if base_table == asso_table
|
90
|
-
return unless @joins[asso_table].nil?
|
91
|
-
|
92
|
-
@join_count += 1
|
93
|
-
base_table_alias = new_asso_chain ? base_table : table_name_to_alias(base_table)
|
94
|
-
asso_table_alias = format('A%02d', @join_count)
|
95
|
-
|
96
|
-
if asso_ref.belongs_to?
|
97
|
-
if asso_ref.polymorphic?
|
98
|
-
join_cond = "#{base_table_alias}.#{asso_ref.foreign_key} = #{asso_table_alias}.#{poly_asso_type_class.primary_key}"
|
99
|
-
join_cond = "#{base_table_alias}.#{asso_ref.foreign_type} = '#{poly_asso_type_class.name}' and #{join_cond}"
|
100
|
-
else
|
101
|
-
join_cond = "#{base_table_alias}.#{asso_ref.foreign_key} = #{asso_table_alias}.#{asso_ref.klass.primary_key}"
|
102
|
-
end
|
50
|
+
raw_sql = selection_group.to_sql
|
51
|
+
if raw_sql.blank?
|
52
|
+
@model_class.all
|
103
53
|
else
|
104
|
-
|
105
|
-
join_cond = "#{asso_table_alias}.#{asso_ref.type} = '#{base_class.name}' and #{join_cond}" if asso_ref.type
|
54
|
+
@model_class.from("(#{raw_sql}) AS #{@model_table_name}")
|
106
55
|
end
|
107
|
-
@joins[asso_table] = [@join_count, asso_table, join_cond]
|
108
56
|
end
|
109
57
|
|
110
|
-
|
111
|
-
# then produce a ConditionGroup
|
112
|
-
def parse_search_parameters(attribute, value)
|
113
|
-
# handle _or_ parameters
|
114
|
-
attributes = attribute.split(@config[:or_separator])
|
115
|
-
if attributes.size > 1
|
116
|
-
cg = ConditionGroup.new
|
117
|
-
cg.set_relation(:or)
|
118
|
-
attributes.each do |a|
|
119
|
-
cg.add(parse_search_parameters(a, value))
|
120
|
-
end
|
121
|
-
return cg
|
122
|
-
end
|
123
|
-
|
124
|
-
# handle direct fields
|
125
|
-
unless attribute =~ /\./
|
126
|
-
condition = build_single_condition(@model_class, @model_class.table_name, attribute, value)
|
127
|
-
return condition
|
128
|
-
end
|
129
|
-
|
130
|
-
# handle association fields
|
131
|
-
association_fields = attribute.split(/\./)
|
132
|
-
field = association_fields.pop
|
58
|
+
private
|
133
59
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
145
76
|
else
|
146
|
-
|
147
|
-
insert_join(base_class, current_association, new_asso_chain)
|
148
|
-
base_class = current_association.klass
|
77
|
+
sg.add_item(SelectionItem.new(@config, @model_class, fields.first, value))
|
149
78
|
end
|
150
79
|
|
151
|
-
|
80
|
+
selection_group.add_child(sg)
|
152
81
|
end
|
153
82
|
|
154
|
-
|
155
|
-
build_single_condition(base_class, association_alias, field, value)
|
83
|
+
selection_group
|
156
84
|
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# This class holds a single condition
|
160
|
-
class ConditionItem
|
161
|
-
attr_reader :field, :verb, :value
|
162
|
-
|
163
|
-
def initialize(field, verb, value)
|
164
|
-
@field = field
|
165
|
-
@verb = verb
|
166
|
-
@value = value
|
167
|
-
end
|
168
|
-
end
|
169
85
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
if item
|
176
|
-
@condition_item = item
|
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
|
177
91
|
else
|
178
|
-
|
92
|
+
raise 'encountered new version of Rails'
|
179
93
|
end
|
180
94
|
end
|
181
95
|
|
182
|
-
def
|
183
|
-
|
184
|
-
|
185
|
-
@children << condition_group if condition_group
|
186
|
-
end
|
187
|
-
|
188
|
-
def set_relation(and_or)
|
189
|
-
raise "It's not needed to set relation for leaf node" if leaf?
|
190
|
-
|
191
|
-
@relation = and_or
|
192
|
-
end
|
193
|
-
|
194
|
-
def leaf?
|
195
|
-
@condition_item ? true : false
|
196
|
-
end
|
197
|
-
|
198
|
-
def empty?
|
199
|
-
@children && @children.empty? ? true : false
|
200
|
-
end
|
201
|
-
|
202
|
-
def to_ar_condition
|
203
|
-
condition = []
|
204
|
-
if leaf?
|
205
|
-
i = @condition_item
|
206
|
-
condition << "#{i.field} #{i.verb}"
|
207
|
-
condition[0] << (i.verb == 'in' ? ' (?)' : ' ?')
|
208
|
-
condition << i.value
|
209
|
-
else
|
210
|
-
tmp_conditions = @children.map(&:to_ar_condition)
|
211
|
-
tmp_condition_str = tmp_conditions.map(&:first).join(" #{@relation} ")
|
212
|
-
condition << "(#{tmp_condition_str})"
|
213
|
-
tmp_conditions.each do |t|
|
214
|
-
(1..(t.length - 1)).each do |index|
|
215
|
-
condition << t[index]
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
condition
|
96
|
+
def table_name_to_alias(table_name)
|
97
|
+
format('A%02d', @joins[table_name][0])
|
220
98
|
end
|
221
99
|
end
|
222
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
|