rails-simple-search 1.1.9 → 1.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c180305a173dc76d61b6bf88048d7b4071b374b0ca3d18e68179f57516e00bc8
4
- data.tar.gz: 36296809170c80c856bb2b300663faae462a11c4fa211803d9386560d605ffba
3
+ metadata.gz: 7bbf2eecbbb512c647a434d6f6d1f928652cfeafa00c8c52226a88c1ec6bc23c
4
+ data.tar.gz: 8b32419b7e0daa8baa581dfd96e04b08513960bbc1a628bda321c12493de6a08
5
5
  SHA512:
6
- metadata.gz: 8a8d8986df1dc830c910f3812968102ab527d9b70fc6cc7d4d8e3550eeeb20b3334c6f1d4d51b06a4d86295dac99a16eb8e94d880f653dada4337244217b098c
7
- data.tar.gz: c03cb28f7e8349be45aa3ff441d811fc4ab0b20b57951c41b7440daacdc9d1a48cbe018fb4d5166c61a9ee40b4d6b1efff46ddaeb60dcacee9eef28d33d8014b
6
+ metadata.gz: 9032b02b2027ee62b51680ed8310ccf7579a8eef33494f6536dfc91f414f853bff2496ee53fb875dccfd52f84e60dacea7d3221eba27d51bc5ceafc73873be63
7
+ data.tar.gz: 796835f0e60438a36e52783eac52e0fdecf9e4ff3fd9789aa53f95396cdae797d1567c9477dea2fb3e773893950bc8aea54b3ed1f7f0c54b4c164deab0c1c28f
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ ## What is rails-simple-search?
2
+ rails-simple-search is a Ruby gem. It can help Ruby on Rails developers **quickly**
3
+ implement searching/filtering function against database. If you're not looking
4
+ for a full-text searching solution, this plugin will most probably **satisfy all**
5
+ your searching requirement.
6
+
7
+ ## Why?
8
+ From time to time, I need to build pages to show a list of narrowed down records
9
+ from a database table by giving some searching criteria on some columns of the
10
+ table and/or of some referencing tables. Before I implemented this plugin, I usually
11
+ do the searching in the following way:
12
+
13
+ 1. Use <%= form_tag %> to build a form in the view
14
+ 2. Get the searching criteria from the params hash individually in the controller
15
+ and put them into instance variables to be used in view
16
+ 3. Build the SQL WHERE clause and sometimes the JOIN clause according to the
17
+ values from the form
18
+ 4. Run the find(:all, :conditions => [xxxxxxxx], :joins => "xxxxxxxx") with the
19
+ WHERE and JOIN clauses
20
+
21
+ After having used this pattern a few times, I realized I could DRY it to make
22
+ future coding of this kind of searching **much simpler**. That's where the
23
+ rails-simple-search plugin comes in.
24
+
25
+ ## Installation
26
+ 1. Put the following line into the Gemfile of your project
27
+ ```
28
+ gem 'rails-simple-search'
29
+ ```
30
+
31
+ 2. Install the gems in Gemfile
32
+ ```
33
+ $ bundle install
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ Let's suppose we have models of User, Address, Post and Comment. User model has_one
39
+ address, has_many posts, and has_many comments; Post model has_many comments; Comment
40
+ model belongs_to an author(of model User). We'd like to search for users according
41
+ to any combination of the following criteria:
42
+
43
+ * part of the user's email addrsss
44
+ * part of the first name or last name
45
+ * range of the user's birth date
46
+ * part of the city name of the user's address
47
+ * state of the user's address
48
+ * part of the name of any authors who commented the user's any posts
49
+
50
+ The following is how we implement this searching function with rails-simple-search:
51
+
52
+ 1. Include gem into Gemfile
53
+ ```
54
+ gem 'rails-simple-search'
55
+ ```
56
+
57
+ 2. Code in model (app/model/search.rb):
58
+ ```
59
+ class Search < RailsSimpleSearch::Base
60
+ end
61
+ ```
62
+
63
+ 3. Code in controller:
64
+ ```
65
+ @search = Search.new(User, params[:search])
66
+ @users = @search.run.order('email')
67
+ ```
68
+
69
+ 4. Code in views:
70
+ ```
71
+ <% form_for @search, url => "/xxxxxx", data: {turbo: false} do |f| %>
72
+
73
+ <%=f.label :email %>
74
+ <%=f.text_field :email %>
75
+
76
+ <%=f.label "first name or last name" %>
77
+ <%=f.text_field "first_name_or_last_name" %>
78
+
79
+ <%=f.label :from_birth_date %>
80
+ <%=f.text_field :birth_date_greater_than_equal_to %>
81
+
82
+ <%=f.label :to_birth_date %>
83
+ <%=f.text_field :birth_date_less_than_equal_to %>
84
+
85
+ <%=f.label :state%>
86
+ <%=f.select "address.state_id", [['AL', 1], ...] %> <!-- address is an association of model User -->
87
+
88
+ <%=f.label :city %>
89
+ <%=f.text_field "address.city" %> <!-- address is an association of model User -->
90
+
91
+ <%=f.label "name of any one who commented to my posts" %>
92
+ <%=f.text_field "posts.comments.author.first_name_or_posts.comments.author.last_name" %>
93
+ <!-- the associations could go even deeper, isn't it POWERFUL? -->
94
+
95
+ <%=f.submit %>
96
+ <% end %>
97
+
98
+ <% @users.each do |user| %>
99
+ <%= # show the attributes of user %>
100
+ <% end %>
101
+ ```
102
+
103
+ 5. Add route for the post to url "/xxxxxx" (config/route.rb)
104
+ ```
105
+ post "/xxxxxx" => "yyyyyyy#zzzzzz"
106
+ ```
107
+
108
+ ## Demo projects
109
+ There are 2 demo projects for this gem, one for [Rails 5](https://github.com/yzhanginwa/demo_app_for_rails_simple_search)
110
+ and one for [Rails 7](https://github.com/yzhanginwa/rails_simple_search_demo). You are encouraged to clone them to your local and
111
+ get a feel of the power of rails-simple-search.
112
+
113
+ ## Version history
114
+ For rails 2.x.x applications, you might want to use the version 0.9.0.
115
+
116
+ From version 0.9.1 to 0.9.7, Rails 3 is supported.
117
+
118
+ From version 0.9.8 on, this gem started to support Rails 4. Version 0.9.8 is tested under Rails 4.1.1, and version 0.9.9 fixed an issue under
119
+ Rails 4.2.
120
+
121
+ From version 1.1.0 on, we started to support the "or" relation, e.g., we can use field like "first_name_or_last_name".
122
+
123
+ From version 1.1.3 on, we started to support Rails 5.
124
+
125
+ For Rails 7, please use version 1.1.9.
126
+
127
+ ## License
128
+
129
+ Copyright &copy; 2012 [Ethan Zhang], released under the MIT license
@@ -1,9 +1,10 @@
1
1
  require_relative 'sql_handler'
2
2
 
3
3
  module RailsSimpleSearch
4
- DEFAULT_CONFIG = { :exact_match => [],
5
- :or_separator => '_or_'
6
- }
4
+ DEFAULT_CONFIG = {
5
+ exact_match: [],
6
+ or_separator: '_or_'
7
+ }
7
8
 
8
9
  module FixModelName
9
10
  def model_name
@@ -16,16 +17,16 @@ module RailsSimpleSearch
16
17
 
17
18
  def self.inherited(subclass)
18
19
  class << subclass
19
- # in rails 3, the call to "form_for" invokes the model_name
20
- def model_name
21
- ActiveModel::Name.new(self)
22
- end
20
+ # in rails 3, the call to "form_for" invokes the model_name
21
+ def model_name
22
+ ActiveModel::Name.new(self)
23
+ end
23
24
  end
24
-
25
+
25
26
  # to fix an issues in rails 4.2
26
27
  subclass.send(:include, RailsSimpleSearch::FixModelName)
27
28
  end
28
-
29
+
29
30
  def self.pre_process(model_name, &procedure)
30
31
  model_name.each { |internal_model_name| pre_process(internal_model_name, &procedure) } if model_name.is_a?(Array)
31
32
  @pre_processors ||= {}
@@ -37,7 +38,7 @@ module RailsSimpleSearch
37
38
  @pre_processors[model_name]
38
39
  end
39
40
 
40
- def initialize(model_class, criteria={}, config={})
41
+ def initialize(model_class, criteria = {}, config = {})
41
42
  @criteria = sanitize_criteria(criteria)
42
43
  @config = DEFAULT_CONFIG.merge(config)
43
44
  @config[:exact_match] = [@config[:exact_match]] unless @config[:exact_match].respond_to?(:map!)
@@ -49,21 +50,19 @@ module RailsSimpleSearch
49
50
  end
50
51
 
51
52
  def load_database_handler(model_class)
52
- if model_class.ancestors.include?(ActiveRecord::Base)
53
- RailsSimpleSearch::Base.send(:include, RailsSimpleSearch::SqlHandler)
54
- else
55
- raise("RailsSimpleSearch only supports ActiveRecord for now")
56
- end
53
+ raise("RailsSimpleSearch only supports ActiveRecord for now") unless model_class.ancestors.include?(ActiveRecord::Base)
54
+
55
+ RailsSimpleSearch::Base.send(:include, RailsSimpleSearch::SqlHandler)
57
56
  end
58
-
57
+
59
58
  def count
60
59
  @count || 0
61
60
  end
62
61
 
63
- def add_conditions(h={})
64
- @criteria.merge!(h.stringify_keys)
62
+ def add_conditions(hash = {})
63
+ @criteria.merge!(hash.stringify_keys)
65
64
  end
66
-
65
+
67
66
  def remove_criteria(key)
68
67
  key.each { |internal_key| remove_criteria(internal_key) } if key.is_a?(Array)
69
68
  @criteria.delete(key.to_s)
@@ -73,43 +72,33 @@ module RailsSimpleSearch
73
72
  @criteria[key.to_s] = value
74
73
  end
75
74
 
76
- private
75
+ private
77
76
 
78
77
  def method_missing(method, *args)
79
78
  method_str = method.to_s
80
79
  if method_str =~ /^([^=]+)=$/
81
- @criteria[$1.to_s] = args[0]
82
- else
80
+ @criteria[::Regexp.last_match(1).to_s] = args[0]
81
+ else
83
82
  @criteria[method_str]
84
83
  end
85
84
  end
86
85
 
87
86
  def parse_field_name(name)
88
- result = {}
89
87
  if name =~ /^(.*)?((_(greater|less)_than)(_equal_to)?)$/
90
- result[:field_name] = $1
91
- if $4 == 'greater'
92
- result[:operator] = ">"
93
- else
94
- result[:operator] = "<"
95
- end
96
- if $5
97
- result[:operator] << "="
98
- end
88
+ field_name = ::Regexp.last_match(1)
89
+ operator = (::Regexp.last_match(4) == 'greater' ? '>' : '<')
90
+ operator << '=' if ::Regexp.last_match(5)
99
91
  else
100
- result[:field_name] = name
92
+ field_name = name
101
93
  end
102
- result
94
+
95
+ [field_name, operator]
103
96
  end
104
-
97
+
105
98
  def sanitize_criteria(criteria)
106
- criteria = criteria || {}
99
+ criteria ||= {}
107
100
  c = {}
108
- criteria.each do |key, value|
109
- unless value.blank?
110
- c[key] = value
111
- end
112
- end
101
+ criteria.each { |key, value| c[key] = value unless value.blank? }
113
102
  c.stringify_keys
114
103
  end
115
104
  end
data/lib/sql_handler.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module RailsSimpleSearch
2
- module SqlHandler
3
-
2
+ module SqlHandler
4
3
  def init
5
4
  @table_name = @model_class.table_name
6
5
  @joins = {}
@@ -26,21 +25,21 @@ module RailsSimpleSearch
26
25
  elsif column.respond_to?(:type)
27
26
  column.type == :string || column.type == :text
28
27
  else
29
- raise "encountered new version of Rails"
28
+ raise 'encountered new version of Rails'
30
29
  end
31
30
  end
32
-
31
+
33
32
  def make_joins
34
33
  @joins_str = ''
35
34
  joins = @joins.values
36
- joins.sort! {|a,b| a[0] <=> b[0]}
35
+ joins.sort! { |a, b| a[0] <=> b[0] }
37
36
  joins.each do |j|
38
37
  table = j[1]
39
38
  constrain = j[2]
40
- @joins_str << " inner join #{table} on #{constrain}"
39
+ @joins_str << " inner join #{table} on #{constrain}"
41
40
  end
42
41
  end
43
-
42
+
44
43
  def run_criteria
45
44
  @condition_group = ConditionGroup.new
46
45
  @condition_group.set_relation(:and)
@@ -52,14 +51,11 @@ module RailsSimpleSearch
52
51
  make_joins
53
52
  end
54
53
 
55
- def insert_condition(base_class, attribute, field, value)
56
- name_hash = parse_field_name(field)
57
- field = name_hash[:field_name]
58
- operator = name_hash[:operator]
59
-
54
+ def insert_condition(base_class, field, value)
55
+ field, operator = parse_field_name(field)
60
56
  table = base_class.table_name
61
57
  key = "#{table}.#{field}"
62
-
58
+
63
59
  column = base_class.columns_hash[field.to_s]
64
60
  return nil unless column
65
61
 
@@ -69,7 +65,7 @@ module RailsSimpleSearch
69
65
  verb = 'in'
70
66
  elsif operator
71
67
  verb = operator
72
- elsif text_column?(column) && ! @config[:exact_match].include?((@table_name == table)? field : key)
68
+ elsif text_column?(column) && ! @config[:exact_match].include?((@table_name == table) ? field : key)
73
69
  verb = 'like'
74
70
  value = "%#{value}%"
75
71
  else
@@ -78,29 +74,28 @@ module RailsSimpleSearch
78
74
 
79
75
  ConditionGroup.new(ConditionItem.new(key, verb, value))
80
76
  end
81
-
77
+
82
78
  def insert_join(base_class, asso_ref)
83
79
  base_table = base_class.table_name
84
80
  asso_table = asso_ref.klass.table_name
85
-
81
+
86
82
  @join_count ||= 0
87
- unless base_table == asso_table
88
- if @joins[asso_table].nil?
89
- @join_count += 1
90
- if asso_ref.belongs_to?
91
- @joins[asso_table] =[@join_count, asso_table, "#{base_table}.#{asso_ref.foreign_key} = #{asso_table}.#{asso_ref.klass.primary_key}"]
92
- else
93
- join_cond = "#{base_table}.#{base_class.primary_key} = #{asso_table}.#{asso_ref.foreign_key}"
94
- join_cond = "#{asso_table}.#{asso_ref.type} = '#{base_class.name}' and #{join_cond}" if asso_ref.type
95
- @joins[asso_table] = [@join_count, asso_table, join_cond]
96
- end
97
- end
83
+ return if base_table == asso_table
84
+ return unless @joins[asso_table].nil?
85
+
86
+ @join_count += 1
87
+ if asso_ref.belongs_to?
88
+ @joins[asso_table] =[@join_count, asso_table, "#{base_table}.#{asso_ref.foreign_key} = #{asso_table}.#{asso_ref.klass.primary_key}"]
89
+ else
90
+ join_cond = "#{base_table}.#{base_class.primary_key} = #{asso_table}.#{asso_ref.foreign_key}"
91
+ join_cond = "#{asso_table}.#{asso_ref.type} = '#{base_class.name}' and #{join_cond}" if asso_ref.type
92
+ @joins[asso_table] = [@join_count, asso_table, join_cond]
98
93
  end
99
94
  end
100
-
95
+
101
96
  def parse_attribute(attribute, value)
102
97
  attributes = attribute.split(@config[:or_separator])
103
- if(attributes.size > 1)
98
+ if attributes.size > 1
104
99
  cg = ConditionGroup.new
105
100
  cg.set_relation(:or)
106
101
  attributes.each do |a|
@@ -110,25 +105,22 @@ module RailsSimpleSearch
110
105
  end
111
106
 
112
107
  unless attribute =~ /\./
113
- field = attribute
114
- condition = insert_condition(@model_class, attribute, field, value)
108
+ condition = insert_condition(@model_class, attribute, value)
115
109
  return condition
116
- end
110
+ end
117
111
 
118
112
  association_fields = attribute.split(/\./)
119
113
  field = association_fields.pop
120
114
 
121
115
  base_class = @model_class
122
- while (association_fields.size > 0)
116
+ until association_fields.empty?
123
117
  association_fields[0] = base_class.reflect_on_association(association_fields[0].to_sym)
124
118
  insert_join(base_class, association_fields[0])
125
119
  base_class = association_fields.shift.klass
126
120
  end
127
121
 
128
- condition = insert_condition(base_class, attribute, field, value)
129
- return condition
122
+ insert_condition(base_class, field, value)
130
123
  end
131
-
132
124
  end
133
125
 
134
126
  class ConditionItem
@@ -142,7 +134,7 @@ module RailsSimpleSearch
142
134
  end
143
135
 
144
136
  class ConditionGroup
145
- def initialize(item=nil)
137
+ def initialize(item = nil)
146
138
  if item
147
139
  @condition_item = item
148
140
  else
@@ -150,17 +142,15 @@ module RailsSimpleSearch
150
142
  end
151
143
  end
152
144
 
153
- def add(cg)
154
- if leaf?
155
- raise "It's not allowed to add child into leaf node"
156
- end
157
- @children << cg if cg
145
+ def add(condition_group)
146
+ raise "It's not allowed to add child into leaf node" if leaf?
147
+
148
+ @children << condition_group if condition_group
158
149
  end
159
150
 
160
151
  def set_relation(and_or)
161
- if leaf?
162
- raise "It's not needed to set relation for leaf node"
163
- end
152
+ raise "It's not needed to set relation for leaf node" if leaf?
153
+
164
154
  @relation = and_or
165
155
  end
166
156
 
@@ -169,7 +159,7 @@ module RailsSimpleSearch
169
159
  end
170
160
 
171
161
  def empty?
172
- (@children && @children.empty?) ? true : false
162
+ @children && @children.empty? ? true : false
173
163
  end
174
164
 
175
165
  def to_ar_condition
@@ -177,17 +167,14 @@ module RailsSimpleSearch
177
167
  if leaf?
178
168
  i = @condition_item
179
169
  condition << "#{i.field} #{i.verb}"
180
- if i.verb == 'in'
181
- condition[0] << " (?)"
182
- else
183
- condition[0] << " ?"
184
- end
170
+ condition[0] << (i.verb == 'in' ? ' (?)' : ' ?')
185
171
  condition << i.value
186
172
  else
187
- tmp = @children.map(&:to_ar_condition)
188
- condition << "(" + tmp.map(&:first).join(" #{@relation} ") + ")"
189
- tmp.each do |t|
190
- (1..(t.length-1)).each do |index|
173
+ tmp_conditions = @children.map(&:to_ar_condition)
174
+ tmp_condition_str = tmp_conditions.map(&:first).join(" #{@relation} ")
175
+ condition << "(#{tmp_condition_str})"
176
+ tmp_conditions.each do |t|
177
+ (1..(t.length - 1)).each do |index|
191
178
  condition << t[index]
192
179
  end
193
180
  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: 1.1.9
4
+ version: 1.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yi Zhang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-24 00:00:00.000000000 Z
11
+ date: 2023-12-26 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.
@@ -17,7 +17,7 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - README.rdoc
20
+ - README.md
21
21
  - lib/rails-simple-search.rb
22
22
  - lib/sql_handler.rb
23
23
  homepage: http://github.com/yzhanginwa/rails-simple-search
data/README.rdoc DELETED
@@ -1,95 +0,0 @@
1
- =rails-simple-search
2
-
3
- rails-simple-search is a Ruby gem. It helps you quickly implement searching/filtering function for your web site. This plugin has paginating feature built in. If you're not looking for a full-text searching solution, this plugin will most probably satisfy all your searching requirement.
4
-
5
- From time to time, I need to build pages to show a list of narrowed down records from a database table by giving some searching criteria on some columns of the table and/or of some referencing tables. Before I implemented this plugin, I usually do the searching in the following way:
6
-
7
- 1. Use <%= form_tag %> to build a form in the view
8
- 2. Get the searching criteria from the params hash individually in the controller and put them into instance variable to be used in view
9
- 3. Build the SQL WHERE clause and sometimes the JOIN clause according to the values from the form
10
- 4. Run the find(:all, :conditions => [xxxxxx], :joins => "yyyyyy") with the WHERE and JOIN clauses
11
-
12
- After having used this pattern a few times, I realized I could DRY it to make future coding of this kind of searching much simpler. That's where the rails-simple-search plugin comes in.
13
-
14
- Now implementing the searching/filter page is a lot easier for me. You're see how easy it is by taking a look at the following example. I may give more examples in the future when I have some spare time.
15
-
16
-
17
- == Example
18
-
19
- Let's suppose we have models of User, Address, Post and Comment. User model has_one address and has_many posts; Post model has_many comments. We'd like to search for users according to any combination of the following criteria:
20
-
21
- * part of the user's email addrsss
22
- * range of the user's birth date
23
- * state of the user's address
24
- * part of the name of any authors who commented the user's any posts
25
-
26
- The following is how we implement this searching function with rails-simple-search:
27
-
28
- 1. Include gem into Gemfile
29
-
30
- gem 'rails-simple-search'
31
-
32
- 2. Code in model (app/model/search.rb):
33
-
34
- class Search < RailsSimpleSearch::Base
35
- end
36
-
37
- 3. Code in controller:
38
-
39
- @search = Search.new(User, params[:search])
40
- @users = @search.run.paginate(page: params[:page], per_page: 20).order('email')
41
-
42
- 4. Code in views:
43
-
44
- <% form_for @search, url => "/xxxxxx", data: {turbo: false} do |f| %>
45
-
46
- <%=f.label :email %>
47
- <%=f.text_field :email %>
48
-
49
- <%=f.label "first name or last name" %>
50
- <%=f.text_field "first_name_or_last_name" %>
51
-
52
- <%=f.label :from_birth_date %>
53
- <%=f.text_field :birth_date_greater_than_equal_to %>
54
-
55
- <%=f.label :to_age %>
56
- <%=f.text_field :birth_date_less_than_equal_to %>
57
-
58
- <%=f.label :state%>
59
- <%=f.select "address.state_id", [['AL', 1], ...] %> <!-- address is an association of model User -->
60
-
61
- <%=f.label :post%>
62
- <%=f.text_field "posts.comments.author" %> <!-- the associations could go even deeper, isn't it POWERFUL? -->
63
-
64
- <%=f.submit %>
65
- <% end %>
66
-
67
- <% @users.each do |user| %>
68
- <%= # show the attributes of user %>
69
- <% end %>
70
-
71
- 5. Add route for the post to url "/xxxxxx" (config/route.rb)
72
- post "/xxxxxx" => "yyyyyyy#zzzzzz"
73
-
74
- == Note
75
-
76
- For rails 2.x.x applications, you might want to use the version 0.9.0.
77
-
78
- From version 0.9.1 to 0.9.7, Rails 3 is supported.
79
-
80
- From version 0.9.8 on, this gem started to support Rails 4. Version 0.9.8 is tested under Rails 4.1.1, and version 0.9.9 fixed an issue under
81
- Rails 4.2.
82
-
83
- From version 1.1.0 on, we started to support the "or" relation, e.g., we can use field like "first_name_or_last_name".
84
-
85
- From version 1.1.3 on, we started to support Rails 5.
86
-
87
- For Rails 7, please use version 1.1.7.
88
-
89
- There is a real demo application which you can download and run immediately: https://github.com/yzhanginwa/demo_app_for_rails_simple_search
90
-
91
- For Rails 7 demo, please see here: https://github.com/yzhanginwa/rails_simple_search_demo
92
-
93
- == License
94
-
95
- Copyright (c) 2012 [Yi Zhang], released under the MIT license