rails-simple-search 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README +67 -0
  2. data/lib/rails-simple-search.rb +178 -0
  3. 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
+