New ez_where plugin.

Posted by ezmobius Mon, 30 Jan 2006 23:31:00 GMT

So me and my friend Fabien Franzen have been busy creating the new ez_where plugin. It's been totally revamped with many additions to the ease of use and the syntax.

Big thanks to Fabien for his excellent work extending my original idea and forcing me to rethink this stuf to make it better. Hope someone finds it usefull. As always, this is alpha software so let me knwo of any bugs or any questions you might have.

We have added the ability to do sub queries and nested AND's and OR's. Plus we have replaced find_with_conditions with the shorter ez_find. This method takes a block and can use the :include options to search on multiple models and return them all in one query with your associations included.

These new changes break backwards compatibility, but I think they are worth it. So if you are using the old version of this plugin be ready to refactor a bit ;-). Without further ado, here is the massive README from the distribution.

You can get this version at:

Welcome to the new improved ez_where plugin for rails. This plugin is meant 
to be used as a nice ruby like syntax for creating the :conditions part of an 
ActiveRecord::Base.find. We also add the ActiveRecord::Base.ez_find method.
This method takes a block to simplify single and multi table queries.

articles = Article.ez_find(:all, :include => :author) do |article, author|
  article.title =~ "%Foo Title%"
  author.any do
    name == 'Ezra'
    name == 'Fab'

This will produce :conditions => ["article.title LIKE ? AND 
                   ( = ? OR = ?)",
                   "%Foo Title%", "Ezra", "Fab"]

Basically here is the breakdown of how we map ruby operators 
to SQL operators:

foo == 'bar'           #=> ["foo = ?", 'bar']
foo =~ '%bar'          #=> ["foo LIKE ?", '%bar']
foo <=> (1..5)         #=> ["foo BETWEEN ? AND ?", 1, 5]
id === [1, 2, 3, 5, 8] #=> ["id IN(?)", [1, 2, 3, 5, 8]]
<, >, >=, <= et all will just work like you expect.

There is also the ability to create the conditions in stages so 
you can build up a query:

cond = do
  foo == 'bar'
  baz <=> (1..5)
  id === [1, 2, 3, 5, 8]
@result = Model.find(:all, :conditions=> cond.to_sql)
#=> ["foo = ? AND baz BETWEEN ? AND ? AND id IN (?)",
     "bar", 1, 5, [1, 2, 3, 5, 8]]

You can even do nested sub conditions. condition will use AND 
by default in the sub condition:

cond = :my_table do
  foo == 'bar'
  baz <=> (1..5)
  id === [1, 2, 3, 5, 8]
  condition :my_other_table do
    fiz =~ '%faz%'

@result = Model.find(:all, :conditions=> cond.to_sql)
#=> [" = ? AND my_table.baz BETWEEN ? AND ? 
     AND IN (?) AND (my_other_table.fiz LIKE ?)",
     "bar", 1, 5, [1, 2, 3, 5, 8], "%faz%"]

You can also build multiple Condition objects and join
them together for one monster find:

cond_a = :my_table do
  foo == 'bar'
  condition :my_other_table do
    id === [1, 3, 8]
    foo == 'other bar'
    fiz =~ '%faz%'
#=> [" = ? AND ( IN (?) AND = ?
       AND my_other_table.fiz LIKE ?)", "bar", [1, 3, 8], "other bar", "%faz%"]

cond_b = :my_table do
  active == true
  archived == false

#=> [" = ? AND my_table.archived = ?", true, false]

composed_cond =
composed_cond << cond_a
composed_cond << cond_b
composed_cond << 'fuzz IS NULL'

@result = Model.find(:all, :conditions => composed_cond.to_sql)
#=> ["( = ? AND ( IN (?) AND = ? 
      AND my_other_table.fiz LIKE ?)) AND ( = ? AND my_table.archived = ?)
      AND fuzz IS NULL", "bar", [1, 3, 8], "other bar", "%faz%", true, false]   

You can compose a new condition from different sources:

ar_instance = Author.find(1)

other_cond = :my_table do 
  foo == 'bar'; baz == 'buzz'

cond =
# another Condition
cond.append other_cond
# an array in AR condition format
cond.append ['baz = ? AND bar IS NOT NULL', 'fuzz'], :or
# a raw SQL string
cond.append 'biz IS NULL'
# an Active Record instance from DB or as Value Object
cond.append ar_instance

#(append is aliased to << because of syntax issues 
involving multiple args like :or)

@result = Model.find(:all, :conditions=> cond.to_sql)

#=> ["( = ? AND my_table.baz = ?) OR (baz = ? AND bar IS NOT NULL) 
      AND biz IS NULL AND = ?", "bar", "buzz", "fuzz", 1]

OK there is also other options for doing subconditions. OR is 
aliased to any and any creates a subcondition that uses OR to 
join the sub conditions:

cond = :my_table do
  foo == 'bar'
  any :my_other_table do
    baz === ['fizz', 'fuzz']
    biz == 'boz'

@result = Model.find(:all, :conditions=> cond.to_sql)

#=> [" = ? AND (my_other_table.baz IN (?) 
     OR = ?)",
     "bar", ["fizz", "fuzz"], "boz"]

OK lets look a bit more at ez_find with a few more complex queries:

# all articles written by Ezra. Here you can use a normal AR object
# in the conditions
# session[:user_id] = 2
ezra = Author.find(session[:user_id])    
@articles = Article.ez_find(:all, :include => :author) do |article, author|
  author << ezra # use AR instance to add condition; uses PK value if set: =
#=>["( = ?)", 2]

# all articles written by Ezra, where he himself responds in comments
@articles = Article.ez_find(:all, :include => [:author, :comments]) do |article, author, comment|
  article.author_id ==
  comment.author_id ==   
#=>["(articles.author_id = ?) AND (comments.author_id = ?)", 2, 2]

# any articles written by Fab or Ezra
@articles = Article.ez_find(:all, :include => :author) do |article, author| === ['Fab', 'Ezra']   
#=>["( IN (?))", ["Fab", "Ezra"]]

# any articles written by Fab or Ezra, using subcondition
@articles = Article.ez_find(:all, :include => :author) do |article, author|
  author.any do
    name == 'Ezra'
    name == 'Fab'
#=>["( = ? OR = ?)", "Ezra", "Fab"]

# any articles written by or commented on by Fab, using subcondition
@articles = Article.ez_find(:all, :include => [:author, :comments]) do |article, author, comment|
  article.sub { author_id == 1 }
  comment.outer = :or # set :outer for the comment condition, since it defaults to :and
  comment.sub { author_id == 1 }       
#=>["(articles.author_id = ?) OR (comments.author_id = ?)", 1, 1]

@articles = Article.ez_find(:all, :include => [:author, :comments],
                           :outer => { :comments => :or }, 
                           :inner => { :article => :or}) do |article, author, comment|
  article.sub { author_id == 1; author_id == 2 }
  comment.sub { author_id == 1 } 
["(articles.author_id = ? OR articles.author_id = ?) OR (comments.author_id = ?)", 1, 2, 1]

And finally you can use any and all with ez_condition like this:

cond = Article.ez_condition { active == true; archived == false }
cond.all { body =~ '%intro%'; body =~ '%demo%' }
cond.any { title =~ '%article%'; title =~ '%first%' }

#=>  [" = ? AND articles.archived = ? 
      AND (articles.body LIKE ? AND articles.body LIKE ?) 
      AND (articles.title LIKE ? OR articles.title LIKE ?)",
      true, false, "%intro%", "%demo%", "%article%", "%first%"]

As you can see we can get quite detailed in the queries this can create. Just use your imagination ;-) Also the test cases in the plugin source have many more examples to choose from.

Get it here: ez_where

Tags , , , ,  | 45 comments


  1. Jens-Christian Fischer said about 6 hours later:
    Cool stuff Ezra, this is really way beyond the humble beginnings :-)
  2. Ezra said about 16 hours later:
    Thanks Jens- It has come quite a ways now!
  3. rcoder said about 19 hours later:
    Nice to see you were able to keep this moving after the c.l.r. thread. I'm looking forward to using this on Rails projects in the future. -rcoder (a.k.a lennon)
  4. pmcm said about 20 hours later:
    Wow! This is really lovely. I've always (strangely) been rather fond of SQL, but this looks so much happier in amongst all the other Ruby stuff. Great work!
  5. Ezra said about 20 hours later:
    Big Thanks to rcoder for helping me out in the earlier stages. He gave me the initial inspiration to overide the ruby operators and convert them into sql operators. And the thing has just grown from there.
    Thanks rcoder!
  6. Rob said about 23 hours later:
    Very very cool. However, if you don't mind my two cents: why not find_with_conditions? It's cleaner. After all, we have stuff like options_from_collection_for_select in Rails too. And if you named it find_with_conditions, you could submit a patch and give "the love" (as Rails fanboys say) to everyone.
  7. Ezra Zygmuntowicz said about 24 hours later:

    I don't think that this will make it into core. Its a perfect fit for a plugin though. but who knows? Stranger things have happened.

    Anyway you are right and i have aliased ez_find so you can use find_with_conditions if you want. They will both work the same way. Recheck out the code to get this change.

    I like find_with_conditions but I also wanted something shorter to keep the code lines smaller. you can use either one you want to now.

    I would submit it as a patch but its still alpha software. It does have an extensive test suite but i am sure there will be edge cases that will pop up. I was going to just override the normal find method so it could be used normally or with the new block syntax. But other plugins like the Globalize plugin override find already so i decided not to do that.

    After this thing gets some wider usage and testing by people I might make a patch to core and see what people think about it. But I know what they will say and I think I agree with them. Things like this belong in a plugin. Plugins are easy to use these days and this on doesn't monkey patch any rails internals. It just builds up the :conditions and sends them along to a normal find. This way it is fast because it isn't really doing any heavy lifting.

    Please use this and give feedback so I can improve it. I want it to remain as lightweight as possible. And i am very open to any syntax improvements people offer.

    And since there is good test coverage, I am all for patches too ;-)
  8. Tommy said 1 day later:
    nice site
  9. George Moschovitis said 1 day later:
    Hello Ezra, I wanted to write something like this for a long time. I would like to integrate this in Og (an alternative Ruby ORM system) Please contact me privately if you are interested in this.
  10. Rob said 3 days later:
    ok, and thanks for aliasing it :) Great stuff
  11. paul said 5 days later:
    this site does not display correctly in firefox... :-(
  12. Ezra said 5 days later:
    What doesn't work exactly in firefox? It seems to work fine for me.
  13. Matt said 6 days later:
    Excellent work, Ezra. I had some really ugly query-building code in a drilldown search I built, and this cut the code by 66%. Not to mention a whole lot easier on the eyes. And the site looks great in Firefox. Maybe he doesn't like your design ;)
  14. draed said 6 days later:
    This is extremely cool. I am wondering if there is any support for case insensitivity? basically i want to do a "LOWER(column) = ?" for certain searches.
  15. mithras said 10 days later:
    very lovely. it'd be great to have some support for building queries in a form, or translating a search @params to these conditions. Really appreciate what you've already done, though.
  16. mithras said 14 days later:
    Hmm, can you give some examples of conditionally adding conditions? (as from a form.) Creating a new condition object for every possible condition, and appending them all together, is a bit ugly.
  17. Sean said 16 days later:
    This is a fantastic plugin. Great work! I'm trying to run a find on a model where I don't know what model it is beforehand. To get the right model, I'm doing an eval: @people = eval(person_type + '.find' + ':all, :conditions => cond') cond is a new conditions object I've created with ez_where. I don't know how to get it to work inside an eval. Any thoughts?
  18. Ezra Zygmuntowicz said 16 days later:
    Sean. instead of using a big old eval like that you can use Object.const_get like this:

    person_type = "Doctor"
    @people = Object.const_get(person_type).find :all, :conditions => cond

    That is much better then an eval and accomplishes the saem thing but lets you use the cond object in a normal way.

    Does that help?
  19. Ezra Zygmuntowicz said 16 days later:
    Or even better:

    person_type = "Doctor"
    @people = person_type.constantize.find :all, :conditions => cond

    Isn't ruby fun?
  20. Sean said 17 days later:
    That is perfect. I had no idea. Thank you so much!
  21. Sean said 17 days later:
    Hmm, one more question--how do I pass the results of this into a paginator? Or do I not need to and I should make my own paged links?
  22. Sean said 17 days later:
    No, wait, scratch that. I had an error elsewhere. Passing cond.to_sql to the paginator works as I thought it should. My issue now is more related to STI than to the plugin, so I'll take it back to the Rails list. :) Thank you again!
  23. cpher said 18 days later:
    Wow. Ruby (and Rails) never ceases to amaze me. Great work.
  24. Craig White said 30 days later:
    Have one remaining issue with ez_where plugin trying to concatenate separate columns to match my aggregation and I guess it boils down to precedence.
    my method looks like this...
      def list2
        fi_first_name = params[:client][:first_name]
        fi_last_name = params[:client][:last_name]
        fi_whole_name = params[:client][:wholename]
        cond = do
        first_name == fi_first_name unless fi_first_name == ""
        last_name == fi_last_name unless fi_last_name == ""
        [(first_name||middle_initial||last_name) == fi_whole_name] unless fi_whole_name == ""

    and the log shows the problem...that it only searches the 'first_name' column and not the postgresql combination of the 3 columns...

    rocessing ClientsController#list2 (for at 2006-03-01 11:57:29) [POST]
      Parameters: {"commit"=>"Find", "client"=>{"wholename"=>"George  Washington", "first_name"=>"", "last_name"=>""}, "action"=>"list2", "controller"=>"clients"}
      Client Load (0.003730)   SELECT * FROM clients WHERE (first_name = 'George Washington')
    various attempts to use append << ["(first_name||middle_initial||last_name) = ?", fi_wholename] haven't changed the outcome

    I do note that I can use the rails method of find with this type of aggregation...
    Client.find(:all, :conditions => ["(first_name||middle_initial||last_name) = ?", fi_wholename])
  25. Craig White said 30 days later:
    boy - that came out ugly...I guess I didn't realize that the comments wouldn't respect line breaks/indentations/spacking etc. necessary for code. Sorry
  26. Ezra said 30 days later:
    Craig- You can't use the || ops like that on the left hand side of an expression. They get eval'ed by ruby as or statements. Also you have the whole thing wrapped in an array, the rhside and the lhside.
    Try this:

        cond = do
        first_name == fi_first_name unless fi_first_name == ""
        last_name == fi_last_name unless fi_last_name == ""
    cond<<["(first_name||middle_initial||last_name) = ?", fi_wholename]
  27. Craig White said 30 days later:
    OK - your way... controller code
    27  def list2
    28    fi_first_name = params[:client][:first_name]
    29    fi_last_name = params[:client][:last_name]
    30    fi_whole_name = params[:client][:wholename]
    32    cond = :table_name => :clients do
    33    first_name == fi_first_name unless fi_first_name == ""
    34    last_name == fi_last_name unless fi_last_name == ""
    35    cond<<["(first_name||middle_initial||last_name) = ?", fi_whole_name]
    36    end
    38    @myclients = Client.find(:all,
    39      :conditions => cond.to_sql)
    40    @client_pages, @clients = paginate :clients, :per_page => 14
    41  end
    This is the log where autocomplete looks up the characters typed from the combination field...
    Processing ClientsController#auto_complete_for_client_wholename (for at 2006-03-01 19:27:21) [POST]
      Parameters: {"client"=>{"wholename"=>"washing"}, "action"=>"auto_complete_for_client_wholename", "controller"=>"clients"}
      Client Load (0.004297)   SELECT * FROM clients WHERE (LOWER(first_name||middle_initial||last_name) LIKE '%washing%') ORDER BY last_name ASC LIMIT 8
    And this is the log after I execute the 'find' code
    Processing ClientsController#list2 (for at 2006-03-01 19:27:27) [POST]
      Parameters: {"commit"=>"Find", "client"=>{"wholename"=>"George  Washington", "first_name"=>"", "last_name"=>""}, "action"=>"list2", "controller"=>"clients"}
    NoMethodError (You have a nil object when you didn't expect it!
    You might have expected an instance of Array.
    The error occured while evaluating nil.<<):
        .//app/controllers/clients_controller.rb:35:in `list2'
        .//vendor/plugins/ez_where/lib/caboose/condition.rb:32:in `initialize'
        .//app/controllers/clients_controller.rb:32:in `list2'
    ... snip ...
    Of course - I'm not saying that ez_where isn't the best thing for sql find complexities in's just got a problem using joined columns that closing off as an array as you suggest isn't working for me
  28. Jeff Moss said 37 days later:
    This is awesome! I would like to see it integrated into the core, but with a new naming scheme. ez_where is no good, use plain english like "ConditionCollection" or something like that. Actually I typed "class ConditionCollection"... then I went and looked to see if anybody else had already done it. This site is #1 on google for "active-record conditions-object" The :conditions option for Find aught to recognize an object of type conditionscollection, at the most basic, I'd like to be able to do cond =; cond.add(:something => something_obj) MyObjects.find(:all, :conditions => cond)
  29. Ezra Zygmuntowicz said 38 days later:
    Jeff- If you look at the docs a bit more you will see that you can already do that.
    include Caboose::EZ
        cond_a = :my_table do
          foo == 'bar'
          condition :my_other_table do
            id === [1, 3, 8]
            foo == 'other bar'
            fiz =~ '%faz%'
        cond_b = :my_table do
          active == true
          archived == false
        composed_cond =
        composed_cond << cond_a
        composed_cond << cond_b.to_sql
        composed_cond << 'fuzz IS NULL'
    ["( = ? AND ( IN (?) AND = ? 
    AND my_other_table.fiz LIKE ?)) AND ( = ?
     AND my_table.archived = ?) AND fuzz IS NULL", 
    "bar", [1, 3, 8], "other bar", "%faz%", true, false]   
    MyModel.find(:all, :conditions => composed_cond.to_sql)
    We are working on a few new additions to this plugin and a new tutorial that covers all the features. Look for it this weekend.
  30. Kevin said 60 days later:
    When a model has two associations to the same table with different association names, ez_find breaks down because it doesn't use the table alias for the second table. E.g., class Item belongs_to :seller, :class_name=>'User' belongs_to :buyer, :class_name=>'User' end You can't do: Item.ez_find(:all, :include=>[:buyer, :seller]) do |item, buyer, seller| buyer.login =~ 'Ez%' seller.login =~ 'Kevin%' end The ez condition generator creates a where clause like: WHERE ((users.login LIKE 'Ez%') AND (users.login LIKE 'Kevin%')) That second one is actually aliased within the join in Rails 1.1 to "sellers_items". The ez finder should actually generate: WHERE ((users.login LIKE 'Ez%') AND (sellers_items.login LIKE 'Kevin%'))
  31. Kevin said 60 days later:
    When a model has two associations to the same table with different association names, ez_find breaks down because it doesn't use the table alias for the second table.


    class Item
    belongs_to :seller, :class_name=>'User'
    belongs_to :buyer, :class_name=>'User'

    You can't do:
    Item.ez_find(:all, :include=>[:buyer, :seller]) do |item, buyer, seller|
    buyer.login =~ 'Ez%'
    seller.login =~ 'Kevin%'

    The ez condition generator creates a where clause like:
    WHERE ((users.login LIKE 'Ez%') AND (users.login LIKE 'Kevin%'))

    That second one is actually aliased within the join in Rails 1.1 to "sellers_items". The ez finder should actually generate:
    WHERE ((users.login LIKE 'Ez%') AND (sellers_items.login LIKE 'Kevin%'))

  32. said 68 days later:
    Ezra, I can get the ez_find working for a single table but when I include a second table it seems to breakdown (probably because I am not sure that I have the code correct). This is what I have: @search_result = Voter.ez_find(:all, :include => :residences) do |voter, residences| if params[:voter][:gender] != 'U' if params[:voter][:gender] == 'M' voters.gender == 'M' else voters.gender == 'F' end end voters.lastname =~ params[:voter][:lastname] if params[:voter][:lastname] voters.firstname =~ params[:voter][:firstname] if params[:voter][:firstname] voters.ph_home == params[:voter][:ph_home] if (params[:voter][:ph_home] != '000-000-0000' || params[:voter][:ph_home]) residences do residences.number == params[:residence][:number] if params[:residence][:number] residences.streetname == params[:residence][:streetname] if params[:residence][:streetname] residences.streettype == params[:residence][:streettype] if params[:residence][:streettype] == params[:residence][:city] if params[:residence][:city] residences.postalcode == params[:residence][:postalcode] if params[:residence][:postalcode] #end end Since I am very new to this I wonder if you could provide some guidance as to syntax or likely pitfalls. Rick
  33. Ezra Zygmuntowicz said 69 days later:
    Folks, it looks like rails 1.1 broke ez_find for multiple tables in the same query. I will have to look at the thing to find the issue. Stay tuned for an update.
  34. Iktorn said 114 days later:
    Does ez_find support nested includes? This doesn't seem to work:
        @names = Widget.find_with_conditions(:all, :include => [{:aaa => {:bbb => :ccc}}]) do |name, aaa, bbb, ccc|
 =~ '%' + p[:keyword] + '%' unless p[:keyword].nil?
 == p[:ccc] unless p[:c].nil?
    Or is it a Rails 1.1 case?
  35. Ezra said 115 days later:
    Yeah I don't think it works with the nested includes right now. The hash syntax in the nested include is confusing it. Fabien and I are working on a new release of the plugin though so we will try to have this fixed in the next release.
  36. Iktorn said 117 days later:
    Do you know the release date already? :)
  37. ptorrsmith said 153 days later:
    Site now doesn't display well (in fact quite poorly) in IE6.
  38. kadoudal said 234 days later:
    I could find in the doc how to build combined OR conditions on multiple tables.. if !params[:querytext].nil? cond_b = :properties do any :properties do street =~ '%'+params[:querytext]+'%' city =~ '%'+params[:querytext]+'%' zipcode =~ '%'+params[:querytext]+'%' end any :contacts do firstName =~ '%'+params[:querytext]+'%' lastName =~ '%'+params[:querytext]+'%' end end end this gives : properties (street OR city OR zipcode) AND contacts (firstName OR lastName) how I should write it to get : properties (street OR city OR zipcode) OR contacts (firstName OR lastName) thanks for anyone help... kad
  39. Tom said 247 days later:
    The between operator is acting a bit weird. It seems to only allow a single digit. cond = :residences do rent <=> (params[:rent]) end Parameters: {"bedrooms"=>"", "x"=>"81", "y"=>"19", "action"=>"quicksearch", "bathrooms"=>"", "controller"=>"residences", "rent"=>"400..500"} Residence Load (0.000399) SELECT * FROM residences WHERE (( BETWEEN '4' AND '0')) 
  40. Ezra said 249 days later:

    You are not passing a ruby range there you are passing a single string. the <=> operator takes a ruby range or array andx not a string.
  41. Mark said 338 days later:
    Does anyone have a code snippet that overwrites a operator to perform case insensitive searches? Something like: Operator foo =i 'bar' #=> ["lower(foo) = lower(?)", 'bar'] foo =~i '%bar' #=> ["lower(foo) like lower(?)", '%bar'] This might make no sense at all because I'm just a ROR newbie.
  42. Ezra said 338 days later:
    Mark- that is already built into the latest ez_where. Make sure to grab the ez-where package from rubyforge svn. Then you can do this:
    foo.nocase =~ '%bar'
  43. Mark said 351 days later:
    Hi all, I'm using twice the select_date function in my view to select a date from and a date until for a date property in my model called "written_on".

    <%= select_date nil, :prefix => "written_from", :order => [:day, :month, :year], :include_blank => true %>

    <%= select_date, :prefix => "written_until", :order => [:day, :month, :year], :include_blank => true %>

    In my model, I want to create a date range if the user has selected valid values for both the date written_from and the date written_until. The POST parameters for written_from and written_until look like this:

    Parameters: {"commit"=>"Find", "written_until"=>{"month"=>"1", "day"=>"20", "year"=>"2007"}, "action"=>"find_proposals", "controller"=>"proposals", "proposal"=>{"client_firstname"=>"", "client_surname"=>"", "city"=>"", "box"=>"", "postal_code"=>"", "number"=>"", "units"=>"", "policy_number"=>"", "street"=>"", "agent_id"=>""}, "written_from"=>{"month"=>"1", "day"=>"2", "year"=>"2007"}}

    I tried to create the dates like this:

    written_from = Date.civil(p[:written_from][:year], p[:written_from][:mon], p[:written_from][:day])
    written_until = Date.civil(p[:written_until][:year], p[:written_until][:mon], p[:written_until][:day])

    but that doesn't seem to work. The ez_where condition that I want would look something like:

    cond = do written_on <=> (date_from..date_until) unless date_from.nil? || date_until.nil? end

  44. Jason said 389 days later:
    This is probably more of a ruby question, but how do I dynamically refer to the column name, such as column_name='last_name' value='Smith' conditions = do column_name == value end (I don't want to set 'colum_name == value', I want to say 'last_name == value' but I dont want to hard code in 'last_name')
  45. we live together said 462 days later:
    Predicted values in template editing can be done through any PHP script?

(leave url/email »)

   Preview comment