Matt Wynne
2008-Aug-15 10:28 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
Hi TDD Fans,
I''m pretty new to Ruby / RSpec / Rails but not to TDD.
This is more of a general ''how do you do good design in a rails
app''
question than an rspec-specific question. I''m asking it here because
I know this list is read by lots of people who care about good
design, but please feel free to point me somewhere else if you think
it''s not relevant to this list.
Probably through my inexperience with the language / framework, I''m
finding that I''m tending to clutter my controllers with SQL-specific
stuff.
e.g.
def get_cities
City.paginate(:all, get_find_params.merge!( :page => params
[:page] ))
end
def get_find_params
find_params = { :order => get_order_clause }
if params[:name] || params[:last_24]
find_params.merge! :conditions => get_conditions
end
return find_params
end
def get_conditions
"name like ''%#{params[:name]}%''" +
(params[:last_24] ? " AND
created_at >= ''#{DateTime.now - 1.days}''" :
"")
end
def get_order_clause
(params[:sort] ? ''created_at DESC, '' : "") +
''name ASC''
end
This is obviously horribly brittle to write specs for, but I''m not
really sure what I should do instead...
How do I get my models to encapsulate this stuff, especially given
I''m using the will_paginate plug-in?
Any tips / pointers greatly appreciated.
cheers,
Matt
http://blog.mattwynne.net
http://songkick.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://rubyforge.org/pipermail/rspec-users/attachments/20080815/8647e5e3/attachment-0001.html>
David Chelimsky
2008-Aug-15 11:25 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
On Fri, Aug 15, 2008 at 5:28 AM, Matt Wynne <matt at mattwynne.net> wrote:> Hi TDD Fans, > I''m pretty new to Ruby / RSpec / Rails but not to TDD. > This is more of a general ''how do you do good design in a rails app'' > question than an rspec-specific question. I''m asking it here because I know > this list is read by lots of people who care about good design, but please > feel free to point me somewhere else if you think it''s not relevant to this > list. > Probably through my inexperience with the language / framework, I''m finding > that I''m tending to clutter my controllers with SQL-specific stuff. > e.g. > def get_cities > > City.paginate(:all, get_find_params.merge!( :page => params[:page] )) > > end > > def get_find_params > find_params = { :order => get_order_clause } > > if params[:name] || params[:last_24] > find_params.merge! :conditions => get_conditions > end > > return find_params > > end > > def get_conditions > "name like ''%#{params[:name]}%''" + (params[:last_24] ? " AND > created_at >= ''#{DateTime.now - 1.days}''" : "") > end > > def get_order_clause > (params[:sort] ? ''created_at DESC, '' : "") + ''name ASC'' > end > This is obviously horribly brittle to write specs for, but I''m not really > sure what I should do instead... > How do I get my models to encapsulate this stuff, especially given I''m using > the will_paginate plug-in? > Any tips / pointers greatly appreciated. > cheers, > MattHey Matt - welcome! The paginate() method lives on the model class, so there''s nothing stopping you from wrapping those calls in methods on the model, slinging around the params object. # CityController def get_cities City.paginate_all(params) end # City def self.paginate_all(params) self.paginate(:all, get_find_params(params).merge!(:page => params[:page])) end etc HTH, David
Matt Wynne
2008-Aug-15 11:46 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
On 15 Aug 2008, at 12:25, David Chelimsky wrote:> Hey Matt - welcome! > > The paginate() method lives on the model class, so there''s nothing > stopping you from wrapping those calls in methods on the model, > slinging around the params object. > > # CityController > > def get_cities > City.paginate_all(params) > end > > # City > > def self.paginate_all(params) > self.paginate(:all, get_find_params(params).merge!(:page => params > [:page])) > end > > etc >Aha. Cool, thanks. For my next question: how do I go about driving out change to the model, spec-first? I''m thinking I would call (in my spec) City.should_receive(:paginate).with(:conditions => "name like ''%# {test_params[:name}%''" .... ) City.paginate_all(test_params) Thereby covering the code in get_find_params() Is that the right approach? cheers, Matt
David Chelimsky
2008-Aug-15 13:29 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
On Aug 15, 2008, at 6:46 AM, Matt Wynne <matt at mattwynne.net> wrote:> On 15 Aug 2008, at 12:25, David Chelimsky wrote: > >> Hey Matt - welcome! >> >> The paginate() method lives on the model class, so there''s nothing >> stopping you from wrapping those calls in methods on the model, >> slinging around the params object. >> >> # CityController >> >> def get_cities >> City.paginate_all(params) >> end >> >> # City >> >> def self.paginate_all(params) >> self.paginate(:all, get_find_params(params).merge!(:page => >> params[:page])) >> end >> >> etc >> > > Aha. Cool, thanks. > > For my next question: how do I go about driving out change to the > model, spec-first? > > I''m thinking I would call (in my spec) > > City.should_receive(:paginate).with(:conditions => "name like > ''%#{test_params[:name}%''" .... ) > City.paginate_all(test_params) > > Thereby covering the code in get_find_params() > > Is that the right approach?That''s probably how I would do it. Might also consider wrapping the params in a separate object that manages the extraction. David> > > cheers, > Matt > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Scott Taylor
2008-Aug-15 23:19 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
On Aug 15, 2008, at 9:29 AM, David Chelimsky wrote:> On Aug 15, 2008, at 6:46 AM, Matt Wynne <matt at mattwynne.net> wrote: > >> On 15 Aug 2008, at 12:25, David Chelimsky wrote: >> >>> Hey Matt - welcome! >>> >>> The paginate() method lives on the model class, so there''s nothing >>> stopping you from wrapping those calls in methods on the model, >>> slinging around the params object. >>> >>> # CityController >>> >>> def get_cities >>> City.paginate_all(params) >>> end >>> >>> # City >>> >>> def self.paginate_all(params) >>> self.paginate(:all, get_find_params(params).merge!(:page => >>> params[:page])) >>> end >>> >>> etc >>> >> >> Aha. Cool, thanks. >> >> For my next question: how do I go about driving out change to the >> model, spec-first? >> >> I''m thinking I would call (in my spec) >> >> City.should_receive(:paginate).with(:conditions => "name like >> ''%#{test_params[:name}%''" .... ) >> City.paginate_all(test_params) >> >> Thereby covering the code in get_find_params() >> >> Is that the right approach? > > That''s probably how I would do it. Might also consider wrapping the > params in a separate object that manages the extraction.That''s how I''ve started doing it - putting sql statements in a module: http://gist.github.com/5675 This allows me to test the sql statements seperately from the actual finder. Also - just to give you the heads up - You should almost never use literal string substitutions in sql statements - it allows for sql injection attacks: http://en.wikipedia.org/wiki/Sql_injection Best, Scott Taylor
Matt Wynne
2008-Aug-18 20:26 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
Thanks Scott. I refactored it today to use what I called a
QueryAdapter, namespaced inside the model. It basically subclasses
Hash, takes the params from the controller into the constructor, and
becomes the hash to be sent to find_all.
I feels much better, as I now have the code that''s coupled to the
database in one place, but I''d welcome feedback:
# VenuesController
def get_venues
Venue.paginate( :all, Venue::QueryAdapter.new(params) )
end
# Responsible for mapping a hash of parameters that will
typically be POSTed to a controller into a hash that can be sent to
find(:all)
# containing SQL clauses in :conditions / :order.
# This helps us decouple the view / controller layers from any
database specific stuff.
class Venue::QueryAdapter < Hash
def initialize(params)
parse params
self.merge!(get_find_params)
end
private
def parse(params)
@sort_column = params[:sort]
@city_id = params[:city_id]
@name = params[:name]
@page = params[:page]
end
def get_find_params
find_params = {}
find_params.merge!( :order => get_order_clause ) if
get_order_clause.length > 0
find_params.merge!( :conditions => get_where_clause ) if
get_where_clause.length > 0
find_params.merge!( :page => @page )
return find_params
end
def get_where_clause
clause = []
clause << "city_id = #{@city_id}" if @city_id
clause << "name like ''%#{@name}%''" if
@name
return clause.join(" AND ")
end
def get_order_clause
clause = []
clause << ''created_on DESC'' if @sort_column ==
''created_at''
clause << ''name ASC''
return clause.join(", ")
end
end
cheers,
Matt
----
http://blog.mattwynne.net
http://songkick.com
In case you wondered: The opinions expressed in this email are my own
and do not necessarily reflect the views of any former, current or
future employers of mine.
On 16 Aug 2008, at 00:19, Scott Taylor wrote:
>
> On Aug 15, 2008, at 9:29 AM, David Chelimsky wrote:
>
>> On Aug 15, 2008, at 6:46 AM, Matt Wynne <matt at mattwynne.net>
wrote:
>>
>>> On 15 Aug 2008, at 12:25, David Chelimsky wrote:
>>>
>>>> Hey Matt - welcome!
>>>>
>>>> The paginate() method lives on the model class, so
there''s nothing
>>>> stopping you from wrapping those calls in methods on the model,
>>>> slinging around the params object.
>>>>
>>>> # CityController
>>>>
>>>> def get_cities
>>>> City.paginate_all(params)
>>>> end
>>>>
>>>> # City
>>>>
>>>> def self.paginate_all(params)
>>>> self.paginate(:all, get_find_params(params).merge!(:page =>
>>>> params[:page]))
>>>> end
>>>>
>>>> etc
>>>>
>>>
>>> Aha. Cool, thanks.
>>>
>>> For my next question: how do I go about driving out change to the
>>> model, spec-first?
>>>
>>> I''m thinking I would call (in my spec)
>>>
>>> City.should_receive(:paginate).with(:conditions => "name
like ''%
>>> #{test_params[:name}%''" .... )
>>> City.paginate_all(test_params)
>>>
>>> Thereby covering the code in get_find_params()
>>>
>>> Is that the right approach?
>>
>> That''s probably how I would do it. Might also consider
wrapping
>> the params in a separate object that manages the extraction.
>
> That''s how I''ve started doing it - putting sql statements
in a module:
>
> http://gist.github.com/5675
>
> This allows me to test the sql statements seperately from the
> actual finder.
>
> Also - just to give you the heads up - You should almost never use
> literal string substitutions in sql statements - it allows for sql
> injection attacks:
>
> http://en.wikipedia.org/wiki/Sql_injection
>
> Best,
>
> Scott Taylor
>
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
Mark Wilden
2008-Aug-18 21:18 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
On Mon, Aug 18, 2008 at 1:26 PM, Matt Wynne <matt at mattwynne.net> wrote:> def get_where_clause > > clause = [] > > clause << "city_id = #{@city_id}" if @city_id > clause << "name like ''%#{@name}%''" if @name >I think you''ve still got SQL injection problems here. ///ark -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080818/0e73f1f2/attachment.html>
Matt Wynne
2008-Aug-19 06:57 UTC
[rspec-users] Proper Encapsulation of SQL WHERE / ORDER BY Clauses
Thanks for the reminder. This stuff is in a protected admin area so I don''t really care, but I should play on the safe side anyhow. cheers, Matt ---- http://blog.mattwynne.net http://songkick.com In case you wondered: The opinions expressed in this email are my own and do not necessarily reflect the views of any former, current or future employers of mine. On 18 Aug 2008, at 22:18, Mark Wilden wrote:> On Mon, Aug 18, 2008 at 1:26 PM, Matt Wynne <matt at mattwynne.net> > wrote: > def get_where_clause > > clause = [] > > clause << "city_id = #{@city_id}" if @city_id > clause << "name like ''%#{@name}%''" if @name > > I think you''ve still got SQL injection problems here. > > ///ark > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080819/addb16d4/attachment.html>