Forum: Ruby on Rails How do we 'Search' in a RESTful world?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
18989f5699fa8619c8e9b301b6cb5088?d=identicon&s=25 BraveDave (Guest)
on 2007-05-11 20:25
(Received via mailing list)
Dear RubyReportist's
I've been beating up the Rails web forums for the last week trying to
understand how 'mature' Rails web developers' elect to allow their
users to 'mince' the data.
We all know that a normal INDEX action in Rails usually does a
Table.find(:all) and there we show the user a list (page by page) of
the 'whole show'.
Now, what happen's when the user wants to just see a part of it, or
sort by a field, or select a result set a group of separate fields?
I've seen code like this:
def self.search(town, beds, min_price, max_price)
  conditions = []
  conditions << ['town = ?', town] if town
  conditions << ['beds = ?', beds] if beds
  conditions << ['price >= ?', min_price] if min_price
  conditions << ['price <= ?', max_price] if max_price
  find(:all, :conditions => [conditions.transpose.first.join(' and '),
*conditions.transpose.last])
But not found anyone that can tie it all together. Are there any
people in this group that would be willing to consult to show me a
simple example of how this could be done?
David Kennedy
DauntlessDavid@gmail.com
775-885-9125
822a498b26a2cb7d1f0f2e7e37ce61b2?d=identicon&s=25 Ed Howland (Guest)
on 2007-05-11 21:29
(Received via mailing list)
On 5/11/07, BraveDave <DauntlessDavid@gmail.com> wrote:
> I've seen code like this:
> simple example of how this could be done?
David,

First off, I like your approach here, I may implement it myself. But
to answer your question as in how I've done it. I needed to be able to
search within associated tables of has_one, has_many and HABTM all
within the same search. I also needed to pass additional filters, that
changed the semantics of the search. I have a metaprogramming based
approach to this. First, I've created a master search class that
defines some class level methods and some instance methods like
to_conditions, to_includes, to_querystring etc. Each form's controller
derives a subclass from that, assigning fields matching those on the
form.

E.g. In my controller:

class HotelController
   class HotelSearcher < MasterSearcher
      attr_accessor :town, :beds, :price  # these are accessable to the
form
   end

   def index
     hotel_searcher = HotelSearcher.new
   end
   def search
     hotel_searcher = HotelSearcher.new(params[:hotel_searcher])
     Hotels.find, :all, :conditions => hotel_seacher.to_conditions #
here be majik
   end

In the view, just do a form_for(@hotel_searcher) or use a FormBuilder,
etc.

To get fancy:
   class HotelSearcher < MasterSearcher
      attr_accessor :town, :beds, :price
      fk_accessor :state, :id, :alias => :state_id
      ignored :filter, :default => 'all'
   end

The fk_accessor uses the model and the field to search on. An optional
alias can be used for things like id's in select lists where the
options are from a lookup table. (They are all called 'id' and you can
only have one of the same name.) There is an additional #to_includes
that gathers the correct include => [] array.

The ignored works for form fields that should not participate in the
conditions clause, but modify the filter in some way (like order or
something else.) You can customize the to_conditions by
   def to_conditions
     super do |conditions|
         # your code goes here
     end
   end

HTH,
Ed

> David Kennedy
> DauntlessDavid@gmail.com
> 775-885-9125
>
>
> >
>


--
Ed Howland
http://greenprogrammer.blogspot.com
18989f5699fa8619c8e9b301b6cb5088?d=identicon&s=25 BraveDave (Guest)
on 2007-05-12 17:19
(Received via mailing list)
Ed,
I feel like I've got a tiger by tail here, and am struggling to keep
up! I've read this post many times and wonder if you'd help me sort it
out?
>First, I've created a master search class that defines some class level methods and some 
instance methods like to_conditions, to_includes, to_querystring etc. Each form's 
controller
derives a subclass from that, assigning fields matching those on the
form.

What does this 'search class' inherit from? Is it like a 'login' table
that has no AR backbone? Did you put it in the app/models folder with
a name like mother_of_all_query_parameters.rb? What blows me away is
that you use the 'form_for' helper and (from what I've read) this
supports ONE AR model table?

So, if you'll let me try to tell the story here...it goes like this:
Your 'MasterSearcher' class is designed to hold stuff like
query :conditions :filters :sort_by, etc. and is uniquely configured
for each 'usage' by adding the correct attr_accessor fields that are
appropriate for the model or combination of models being searched.
When the user clicks on SEARCH the search.controller.index action
fires and presents the index.rhtml where the user fills in search
criteria using the form_for helper. When the user hits the SUBMIT
button it fires the SEARCH action that then delivers the search.rhtml
to show the user the 'goodies' they wanted. I can't see where the
@hotel is that you pass to 'feed' this view?

Could you combine your SEARCH action logic into your INDEX logic
something like this;
def index
  if the params[:hotel_searcher])  are nil
    MasterTable.find(:all)
  else
    do that voodoo that you do so well
  end
end
I'm so new and impressionable to Rails, that I'm striving to be a
'good boy' and only use the 7 CRUD actions.
Thank you,
David
822a498b26a2cb7d1f0f2e7e37ce61b2?d=identicon&s=25 Ed Howland (Guest)
on 2007-05-14 14:40
(Received via mailing list)
On 5/12/07, BraveDave <DauntlessDavid@gmail.com> wrote:
> that has no AR backbone? Did you put it in the app/models folder with
> a name like mother_of_all_query_parameters.rb? What blows me away is
> that you use the 'form_for' helper and (from what I've read) this
> supports ONE AR model table?
>

I actually put it in a module in app/helpers called SearchHelper and a
class called SearchForm. So in reality my code in the controller looks
more like this:
  class HotelSearcher < SearchHelper::SearchForm.
I did it this way to be able to run RSpec tests (which has a
convenient spec:helpers Rake task. Also, it is visible to the
controller and the view.

The form_for doesn't care about AR. The object just has to be
initialized in the controller and respond to method calls for the
fields. That's why I used att_accessor to create them in the class def
in the controller.

> So, if you'll let me try to tell the story here...it goes like this:
> Your 'MasterSearcher' class is designed to hold stuff like
> query :conditions :filters :sort_by, etc. and is uniquely configured
> for each 'usage' by adding the correct attr_accessor fields that are
> appropriate for the model or combination of models being searched.

It actually doesn't hold the sort by but you could do that. The reason
I didn't was because I am using the OpenRico DB grid and it handles
(column) sorting via JavaScript and Ajax call backs. I just get the
conditions from the derived SearchForm and combine it with the stuff
from OpenRico.

> When the user clicks on SEARCH the search.controller.index action
> fires and presents the index.rhtml where the user fills in search
> criteria using the form_for helper. When the user hits the SUBMIT
> button it fires the SEARCH action that then delivers the search.rhtml
> to show the user the 'goodies' they wanted. I can't see where the
> @hotel is that you pass to 'feed' this view?
>
You are right, my example code was missing that. Should have been:
def search
    hotel_searcher = HotelSearcher.new(params[:hotel_searcher])
    @hotels = Hotels.find, :all, :conditions =>
hotel_seacher.to_conditions #
end

> Could you combine your SEARCH action logic into your INDEX logic
> something like this;
> def index
>   if the params[:hotel_searcher])  are nil
>     MasterTable.find(:all)
>   else
>     do that voodoo that you do so well
>   end
> end

Yes, that is possible, but not very Railish or RESTful. You should let
the router make decisions about which actions to call. Almost all the
Rails code I've seen has had a form in the index that POSTs to another
action. Especially if you do Ajax forms.

> I'm so new and impressionable to Rails, that I'm striving to be a
> 'good boy' and only use the 7 CRUD actions.

My approach is really REST agnostic. If by 7 you mean (index, new,
create, show, edit, update, destroy) only index displays a list of
records. Search isn't usually one of these CRUD things, but something
nearly every app I've written has needed.

In REST you have these two actions:
   GET   /articles
     and
   GET   /article/123
The first responds with a list, the second with a view of one
particular element of that list. Note the the index action has no
filter. It is not very modern UI (you'd get back all of the articles
of which there could be thousands or more.) How do you filter? How do
you page the results?

I think Searching and REST are out of each other's scope. Or it seems
that way to me.

Alternatively, you could use PragDave's RADAR approach to architect the
app.

http://pragdave.pragprog.com/pragdave/2007/03/the_...

While not a solution to the search issue, I've heard of using this to
implement a search in the presentation layer that talks to the back
end via REST. Can't find the URL for it.

> Thank you,
> David

Sure.

Hth,
Ed


> >
> > > I've seen code like this:
> > > simple example of how this could be done?
> > to_conditions, to_includes, to_querystring etc. Each form's controller
> >    def index
> > To get fancy:
> > that gathers the correct include => [] array.
> > HTH,
>
>
> >
>


--
Ed Howland
http://greenprogrammer.blogspot.com
9d1f5d2d9de70bd9a934f557dc95a406?d=identicon&s=25 Daniel ----- (liquid)
on 2007-05-14 15:13
(Received via mailing list)
I'm not sure that this answers your question at all, but there is a nice
plugin that helps with searching AR records without the fulltext
searching.
EZWhere by Ezra,  You can start looking at it on his blog at
http://brainspl.at/articles/2006/10/03/nested-join...

HTH
Daniel
822a498b26a2cb7d1f0f2e7e37ce61b2?d=identicon&s=25 Ed Howland (Guest)
on 2007-05-14 15:34
(Received via mailing list)
On 5/14/07, Daniel N <has.sox@gmail.com> wrote:
> I'm not sure that this answers your question at all, but there is a nice
> plugin that helps with searching AR records without the fulltext searching.
> EZWhere by Ezra,  You can start looking at it on his blog at
> http://brainspl.at/articles/2006/10/03/nested-join...
>
> HTH
> Daniel

This is a nice conditions generator and it handles the nested joins
that many I've seen lacked. I like the embedding of order by in the
syntax.

I was trying to solve the first half of the problem (for which this
plugin solves the second 1/2 nicely.) IOW, how do you go from a search
form to a conditions array? Esp. when you have multiple join tables
that participate in the search.

In many cases, if you have a single model to search, you can create it
in the search index action
  @model = MyModel.new
Then use form_for on @model

and then again in the SUBMIT action
   @model = MyModel.new(params[:model]
   ... somehow create the conditions array from the fields in the model.
   ... perhaps via EXWhere above.

But how do you make this DRY? It seems that each form is going to have
to be handled independently, and this make sense, but I wanted to
convert from any form into any conditions array and do it OAOO.

My approach to form processing could use an EZWhere plugin internally,
because I am using metaprogramming to generate the fields.

Ed

> > > out?
> > > that you use the 'form_for' helper and (from what I've read) this
> >
> >
> > > to show the user the 'goodies' they wanted. I can't see where the
> > > something like this;
> > Rails code I've seen has had a form in the index that POSTs to another
> > In REST you have these two actions:
> > that way to me.
> >
> > >
> > > > > Dear RubyReportist's
> > > > > def self.search(town, beds, min_price, max_price)
> > > > > simple example of how this could be done?
> > > > to_conditions, to_includes, to_querystring etc. Each form's controller
> > > >
> etc.
> > > > options are from a lookup table. (They are all called 'id' and you can
> > > >    end
> quoted text -
> > http://greenprogrammer.blogspot.com
> >
> > > >
> >
>


--
Ed Howland
http://greenprogrammer.blogspot.com
This topic is locked and can not be replied to.