More dynamic way? Search Pagination


#1

All,

I’m new to ruby and to rails! :slight_smile: This will give you all a chance to
tear my code apart! Feel free to offer better techniques as necessary!

I have a need to implement somewhat dynamic search across several
different entities.

In one of my controllers, I have an action called search. I have some
code working to do a search based on parameters from a form submit.
Now, I want to make the search routine available to all controllers.
I’m trying to figure out if I can dynamically name variables in ruby or
not based on input args to a function.

Here is the code I have in the controller:

@whereClause = “”
@params[:price_quote].each { | key, value |
if value.length > 0
@whereClause = @whereClause.length == 0 ? “#{key} = #{value}” :
@whereClause << " and #{key} = #{value}"
end
}

if @whereClause.length > 0 then
@price_quote_pages, @price_quotes = paginate :price_quote,
:per_page => 10, :conditions => @whereClause
else
@price_quote_pages, @price_quotes = paginate :price_quote,
:per_page => 10
end

I would like to have a function called “getSearchResults” or something
similar where I pass in the name of the controller and the param hash.
Based on the input args, it would find the proper hash and create the
page objects with the controller name.

Any suggestions?

Michael


#2

On Friday 11 November 2005 7:27 am, Michael wrote:

I would like to have a function called “getSearchResults” or something
similar where I pass in the name of the controller and the param hash.
Based on the input args, it would find the proper hash and create the page
objects with the controller name.

Any suggestions?

Hi Michael,

I’m quite new to Rails myself, but I’ll try to help.

Start by putting a function in app/controllers/application.rb:

private

def dynamic_search(
	options = {
		:controller_name => "default_controller",
		:search_params => {}
	})
	controller_name = options[:controller_name]
	search_params = options[:search_params]

	# (your search code here)

end

Then in any of your app’s controllers you can call it using:

@results = dynamic_search( :controller_name =>
“some_controller”, :search_params => { :key1 => “value1”, :key2 =>
“value2”,
etc})

Using this technique, you could omit either of the parameters passed to
the
function by replacing “default_controller” with the name of, well, your
default controller, and adding code to your search routine to handle the
parameters. Here’s a hint:

for column in Price_quote.content_columns
	...
end

Will let you iterate through the column names of the db table.

Good luck. (The more experienced of you please correct me where I’m
wrong)


#3

Mark,

Thank you for the input.

I already have the column names and values to search on - they were in
the params hash from my original sample. I get them when the user
submits the search form.

@params[:price_quote].each { | key, value | …

What I really would like to know how to do, though, is somehow
dynamically name the objects. For example, If I tell the function that
this is for the price_quote controller, then I want it to create the
proper pagination objects for me. Taking the sample from below, I want
to be able to name “@price_quote_pages” based on the input of the
function. It could be “@lead_pages”, “@activity_pages”, etc…

if @whereClause.length > 0 then

@price_quote_pages, @price_quotes = paginate :price_quote, :per_page =>
10, :conditions => @whereClause

The views rely on the name of these pagination objects to navigate
forward and backward through the records.

All in all, this search actually sucks pretty badly! It isn’t flexible
at all. I had a bug in my original post whereby I didn’t surround the
param values with single quotes (worked great for numeric fields
though)…hahaha. I just need something basic for simple search on
multiple fields without having to hard code the names (e.g.
ActiveRecord.find_all_by…and…and…) and get the results into a
pagination object!

Thanks,

Michael

Mark B. removed_email_address@domain.invalid wrote:
On Friday 11 November 2005 7:27 am, Michael wrote:

I would like to have a function called “getSearchResults” or something
similar where I pass in the name of the controller and the param hash.
Based on the input args, it would find the proper hash and create the page
objects with the controller name.

Any suggestions?

Hi Michael,

I’m quite new to Rails myself, but I’ll try to help.

Start by putting a function in app/controllers/application.rb:

private

def dynamic_search(
options = {
:controller_name => “default_controller”,
:search_params => {}
})
controller_name = options[:controller_name]
search_params = options[:search_params]

(your search code here)

end

Then in any of your app’s controllers you can call it using:

@results = dynamic_search( :controller_name =>
“some_controller”, :search_params => { :key1 => “value1”, :key2 =>
“value2”,
etc})

Using this technique, you could omit either of the parameters passed to
the
function by replacing “default_controller” with the name of, well, your
default controller, and adding code to your search routine to handle the
parameters. Here’s a hint:

for column in Price_quote.content_columns

end

Will let you iterate through the column names of the db table.

Good luck. (The more experienced of you please correct me where I’m
wrong)


#4

On Friday 11 November 2005 10:38 am, Michael wrote:

What I really would like to know how to do, though, is somehow dynamically
name the objects. For example, If I tell the function that this is for the
price_quote controller, then I want it to create the proper pagination
objects for me. Taking the sample from below, I want to be able to name
@price_quote_pages” based on the input of the function. It could be
@lead_pages”, “@activity_pages”, etc…

I don’t think the controller is determined by the paginator. I’m
thinking that
you can pass whatever controller you want to the links generated from
the
paginator in your view:

<%= link_to ‘Previous page’, {:controller => @whatever, :page =>
@price_quote_pages.current.previous, :search => @params[‘search’],
:order_by
=> @params[‘order_by’], :order_dir => @params[‘order_dir’] } if
@price_quote_pages.current.previous %>

and

<%= link_to ‘Next page’, {:controller => @whatever, :page =>
@price_quote_pages.current.next, :search => @params[‘search’], :order_by
=>
@params[‘order_by’], :order_dir => @params[‘order_dir’] } if
@price_quote_pages.current.next %>

Check out the docs for the link_to helper:

http://api.rubyonrails.com/classes/ActionView/Helpers/UrlHelper.html#M000325

link_to probably defaults to the current controller unless told
otherwise.


#5

Hi Michael,

I’m also fairly new to rails, and I have been looking at this same
issue. With some help from the people on rubyonrails irc I put together
the following in application.rb

def search
conditions = “0”
params.delete(“controller”) # is there a better way to extract
these terms???
params.delete(“action”)
params.delete(“page”)
params.each {|key, value| conditions+= " OR #{key} LIKE
‘%#{value}%’" }
@name = self.controller_name()
@item_pages, @items = paginate @name.to_sym, :per_page => 5,
:conditions => conditions
@model = Kernel.const_get(@name.camelize)
render :layout => ‘mylayout’
end

and then I drop the following into each view as search.rhtml

<% for item in @items %>

<% for column in @model.content_columns %> <%=h item.send(column.name) %>
<%end%> <%= link_to 'Show', :action => 'show', :id => item %> <%= link_to 'Edit', :action => 'edit', :id => item %> <%= link_to_remote "Remove", :url=>{ :controller=>"items", :action=>"destroy", :id=>item}, :loading=>"status('item#{item.id}')", :complete=>"new Effect.Fade('item#{item.id}')" %>
<% end %>

<%= pagination_links(@item_pages, {:params=>params}) %>

and now every one of my controllers that has an associated model name
(e.g. recipes_controller and recipe) has search available, e.g.

http://rails/recipes/search?title=trifle

I wonder if this meets all your needs.

CHEERS> SAM


#6

On 11/21/05, Sam J. removed_email_address@domain.invalid wrote:

I’m also fairly new to rails, and I have been looking at this same
issue. With some help from the people on rubyonrails irc I put together
the following in application.rb

I really hope nobody on IRC actually told you to use the code below.
While the method they gave you will work it has an obvious SQL
injection vulnerability.

:conditions => conditions
@model = Kernel.const_get(@name.camelize)
render :layout => ‘mylayout’
end

You should not be trusting the data given in params. You should be
checking key against a list of known good keys, and using ? as
placeholders for values. Maybe something like:

def search
valid_keys = %w’foo bar baz’
conditions = [‘0’]
valid_keys.each do |key|
if params.include?(key)
conditions[0] << " OR #{key} LIKE ?"
conditions << “%#{params[key]}%”
end
end
@name = self.controller_name()
@item_pages, @items = paginate @name.to_sym, :per_page => 5,
:conditions => conditions
@model = Kernel.const_get(@name.camelize)
render :layout => ‘mylayout’
end

That’s untested, but it shouldn’t be vulnerable.

You should never, ever, under any circumstances, trust data that is in
params. You should be checking, filtering, and/or scrubbing all
client data before using it in your SQL queries (protection against
SQL injection) or displaying it on your page (protection against cross
site scripting). Rails makes SQL injection protection easy, by
submitting an array for conditions using ? as placeholders for
variables. I haven’t read the AWDR book, but I’m guessing (hoping)
there is a section on this.