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.
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
+