rails-simple-search 0.9.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.
- data/README +67 -0
- data/lib/rails-simple-search.rb +178 -0
- metadata +68 -0
data/README
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
SimpleSearch
|
2
|
+
============
|
3
|
+
|
4
|
+
SimpleSearch is a Ruby on Rails plugin. It helps you quickly implement searching/filtering function for your web site.
|
5
|
+
This plugin has paginating feature built in. If you're not looking for a full-text searching solution, this plugin will
|
6
|
+
most probably satisfy all your searching requirement.
|
7
|
+
|
8
|
+
From time to time, I need to build pages to show a list of narrowed down records from a database table by giving
|
9
|
+
some searching criteria on some columns of the table and/or of some referencing tables. Before I implemented this
|
10
|
+
plugin, I usually do the searching in the following way:
|
11
|
+
1) Use <%= form_tag %> to build a form in the view
|
12
|
+
2) Get the searching criteria from the params hash individually in the controller and put them into instance variable to be used in view
|
13
|
+
3) Build the SQL WHERE clause and sometimes the JOIN clause according to the values from the form
|
14
|
+
4) Run the find(:all, :conditions => [xxxxxx], :joins => "yyyyyy") with the WHERE and JOIN clauses
|
15
|
+
|
16
|
+
After having used this pattern a few times, I realized I could DRY it to make future coding of this kind of searching
|
17
|
+
much simpler. That's where the SimpleSearch plugin comes in.
|
18
|
+
|
19
|
+
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
|
20
|
+
following example. I may give more examples in the future when I have some spare time.
|
21
|
+
|
22
|
+
|
23
|
+
Example
|
24
|
+
=======
|
25
|
+
|
26
|
+
Let's suppose we have models of User, Address, Post and Comment.
|
27
|
+
User model has_one address and has_many posts; Post model has_many comments
|
28
|
+
We'd like to find users according to any combination of the following criteria:
|
29
|
+
|
30
|
+
1) part of the user's email addrsss
|
31
|
+
2) state of the user's address
|
32
|
+
3) part of the name of any authors who commented the user's any posts
|
33
|
+
|
34
|
+
The following is how we implement this searching function with SimpleSearch :
|
35
|
+
|
36
|
+
Code in model (app/model/search.rb):
|
37
|
+
|
38
|
+
class Search < SimpleSearch::Base
|
39
|
+
end
|
40
|
+
|
41
|
+
Code in controller:
|
42
|
+
|
43
|
+
@search = Search.new(User, params[:search])
|
44
|
+
@users = @search.run(:order => 'email')
|
45
|
+
|
46
|
+
Code in views:
|
47
|
+
|
48
|
+
<% form_for @search do |f| %>
|
49
|
+
|
50
|
+
<%=f.label :email %>
|
51
|
+
<%=f.text_field :email %>
|
52
|
+
|
53
|
+
<%=f.label :state%>
|
54
|
+
<%=f.select "address.state_id", [['AL', 1], ...] %> <!-- address is an association of model User -->
|
55
|
+
|
56
|
+
<%=f.label :post%>
|
57
|
+
<%=f.text_field "posts.comments.author" %> <!-- the associations could go even deeper, isn't it POWERFUL? -->
|
58
|
+
|
59
|
+
<%=f.submit %>
|
60
|
+
<% end %>
|
61
|
+
|
62
|
+
<% @users.each do |user| %>
|
63
|
+
<%= # show the attributes of user %>
|
64
|
+
<% end %>
|
65
|
+
|
66
|
+
|
67
|
+
Copyright (c) 2012 [Yi Zhang], released under the MIT license
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module RailsSimpleSearch
|
2
|
+
DEFAULT_CONFIG = { :exact_match => [],
|
3
|
+
:paginate => true,
|
4
|
+
:page_name => 'page',
|
5
|
+
:offset => 0,
|
6
|
+
:limit => 1000,
|
7
|
+
:per_page => 20
|
8
|
+
}
|
9
|
+
class Base
|
10
|
+
def initialize(model_class, criteria, config={})
|
11
|
+
@model_class = (model_class.is_a?(Symbol) || model_class.is_a?(String))? model_class.to_s.camelize.constantize : model_class
|
12
|
+
@table_name = @model_class.table_name
|
13
|
+
@criteria = criteria.nil? ? {} : criteria
|
14
|
+
sanitize_criteria
|
15
|
+
@config = DEFAULT_CONFIG.merge(config)
|
16
|
+
@joins = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def count
|
20
|
+
@count || 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def pages
|
24
|
+
(count == 0)? 0 : (count / @config[:per_page].to_i + 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def pages_for_select
|
28
|
+
(1..pages).to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_conditions(h={})
|
32
|
+
@criteria.merge!(h)
|
33
|
+
end
|
34
|
+
|
35
|
+
def conditions
|
36
|
+
run_criteria
|
37
|
+
@conditions
|
38
|
+
end
|
39
|
+
|
40
|
+
def joins
|
41
|
+
run_criteria
|
42
|
+
@joins_str
|
43
|
+
end
|
44
|
+
|
45
|
+
def run(option={})
|
46
|
+
run_criteria
|
47
|
+
if @config[:paginate]
|
48
|
+
@count = @model_class.count({:select => "distinct #{@model_class.table_name}.#{@model_class.primary_key}",
|
49
|
+
:conditions => @conditions,
|
50
|
+
:joins => @joins_str }
|
51
|
+
)
|
52
|
+
offset = [((@page || 0) - 1) * @config[:per_page], 0].max
|
53
|
+
limit = @config[:per_page]
|
54
|
+
else
|
55
|
+
offset = @config[:offset]
|
56
|
+
limit = @config[:limit]
|
57
|
+
end
|
58
|
+
|
59
|
+
@model_class.all({:select => "distinct #{@model_class.table_name}.*",
|
60
|
+
:conditions => @conditions,
|
61
|
+
:joins => @joins_str,
|
62
|
+
:offset => @config[:offset],
|
63
|
+
:limit => @config[:limit] }.merge(option)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def method_missing(method, *args)
|
70
|
+
method_str = method.to_s
|
71
|
+
if method_str =~ /^([^=]+)=$/
|
72
|
+
@criteria[$1.to_s] = args[0]
|
73
|
+
else
|
74
|
+
@criteria[method_str]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def make_joins
|
79
|
+
@joins_str = ''
|
80
|
+
@joins = @joins.values
|
81
|
+
@joins.sort! {|a,b| a[0] <=> b[0]}
|
82
|
+
@joins.each do |j|
|
83
|
+
table = j[1]
|
84
|
+
constrain = j[2]
|
85
|
+
@joins_str << " inner join #{table} on #{constrain}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def run_criteria
|
90
|
+
return @conditions unless @conditions.nil?
|
91
|
+
@criteria.each do |key, value|
|
92
|
+
if @config[:page_name].to_s == key.to_s
|
93
|
+
@page = value.to_i
|
94
|
+
@criteria[key] = @page
|
95
|
+
else
|
96
|
+
parse_attribute(key, value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
make_joins
|
101
|
+
end
|
102
|
+
|
103
|
+
def insert_condition(base_class, attribute, field, value)
|
104
|
+
table = base_class.table_name
|
105
|
+
key = "#{table}.#{field}"
|
106
|
+
|
107
|
+
@conditions ||= []
|
108
|
+
column = base_class.columns_hash[field.to_s]
|
109
|
+
|
110
|
+
if !column.text? && value.is_a?(String)
|
111
|
+
value = column.type_cast(value)
|
112
|
+
@criteria[attribute] = value
|
113
|
+
end
|
114
|
+
|
115
|
+
if value.nil?
|
116
|
+
verb = 'is'
|
117
|
+
elsif column.text? && ! @config[:exact_match].include?((@table_name == table)? field : key)
|
118
|
+
verb = 'like'
|
119
|
+
value = "%#{value}%"
|
120
|
+
else
|
121
|
+
verb = '='
|
122
|
+
end
|
123
|
+
|
124
|
+
if @conditions.size < 1
|
125
|
+
@conditions[0] = "#{key} #{verb} ?"
|
126
|
+
@conditions[1] = value
|
127
|
+
else
|
128
|
+
@conditions[0] += " and #{key} #{verb} ?"
|
129
|
+
@conditions << value
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def insert_join(base_class, asso_ref)
|
134
|
+
base_table = base_class.table_name
|
135
|
+
asso_table = asso_ref.klass.table_name
|
136
|
+
|
137
|
+
@join_count ||= 0
|
138
|
+
unless base_table == asso_table
|
139
|
+
if @joins[asso_table].nil?
|
140
|
+
@join_count += 1
|
141
|
+
if asso_ref.belongs_to?
|
142
|
+
@joins[asso_table] =[@join_count, asso_table, "#{base_table}.#{asso_ref.primary_key_name} = #{asso_table}.#{asso_ref.klass.primary_key}"]
|
143
|
+
else
|
144
|
+
@joins[asso_table] = [@join_count, asso_table, "#{base_table}.#{base_class.primary_key} = #{asso_table}.#{asso_ref.primary_key_name}"]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_attribute(attribute, value)
|
151
|
+
unless attribute =~ /\./
|
152
|
+
field = attribute
|
153
|
+
insert_condition(@model_class, attribute, field, value)
|
154
|
+
return
|
155
|
+
end
|
156
|
+
|
157
|
+
association_fields = attribute.split(/\./)
|
158
|
+
field = association_fields.pop
|
159
|
+
|
160
|
+
base_class = @model_class
|
161
|
+
while (association_fields.size > 0)
|
162
|
+
association_fields[0] = base_class.reflect_on_association(association_fields[0].to_sym)
|
163
|
+
insert_join(base_class, association_fields[0])
|
164
|
+
base_class = association_fields.shift.klass
|
165
|
+
end
|
166
|
+
|
167
|
+
insert_condition(base_class, attribute, field, value)
|
168
|
+
end
|
169
|
+
|
170
|
+
def sanitize_criteria
|
171
|
+
@criteria.keys.each do |key|
|
172
|
+
if @criteria[key].nil? || @criteria[key].blank?
|
173
|
+
@criteria.delete(key)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails-simple-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Yi Zhang
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-11 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: A very simple and light get to quick build search function in rails
|
23
|
+
email: yzhang.wa@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- lib/rails-simple-search.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/yzhanginwa/rails-simple-search
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
hash: 3
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.6.2
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: rails-simple-search is awesome!
|
67
|
+
test_files: []
|
68
|
+
|