Dots in named_scope search blow up query.

Hi all,

I got a strange notification last night using hoptoad. For a rails app running rails 2.3.5 on a mysql database.

I have written a search engine for a site that combines a few named scopes. One of those named scopes is this;

  named_scope :matching, lambda { |word| { :joins => :consumer, :conditions => ['( like :word) OR (jobs.description like :word) OR (users.zip_code like :word) OR ( like :word)', { :word => "%#{word}%"}]} }

All works fine and the statement above (in combination with another default named scope) generates a nice sql statement;

SELECT `jobs`.* FROM `jobs` INNER JOIN `users` ON `users`.id = `jobs`.consumer_id AND (`users`.`type` = 'Consumer' ) WHERE (((`users`.locale = 'nl') AND (( like '%test%') OR (jobs.description like '%test%') OR (users.zip_code like '%test%') OR ( like '%test%'))) AND (active = 1)) ORDER BY auction_ends_at <= '2010-02-26 09:25', `jobs`.updated_for_search_at DESC LIMIT 0, 8

However; when a user would enter a dot in the string to search it all goes wrong. The dot inside the search string, seems to blow up the query like this;

SELECT `jobs`.`id` AS t0_r0, `jobs`.`consumer_id` AS t0_r1, `jobs`.`name` AS t0_r2, `jobs`.`description` AS t0_r3, `jobs`.`field_id` AS t0_r4, `jobs`.`auction_ends_at` AS t0_r5, `jobs`.`materials_provided` AS t0_r6, `jobs`.`desired_first_day` AS t0_r7, `jobs`.`price_type_id` AS t0_r8, `jobs`.`created_at` AS t0_r9, `jobs`.`updated_at` AS t0_r10, `jobs`.`active` AS t0_r11, `jobs`.`best_bid` AS t0_r12, `jobs`.`bid_count` AS t0_r13, `jobs`.`updated_for_search_at` AS t0_r14, `jobs`.`highlight` AS t0_r15, `jobs`.`featured_till` AS t0_r16, `jobs`.`thumbnail` AS t0_r17, `jobs`.`expire_notification_sent` AS t0_r18, `jobs`.`worker_id` AS t0_r19, `jobs`.`asap` AS t0_r20, `jobs`.`worker_reminder_count` AS t0_r21, `jobs`.`next_worker_reminder_at` AS t0_r22, `jobs`.`view_count` AS t0_r23, `users`.`id` AS t1_r0, `users`.`username` AS t1_r1, `users`.`email` AS t1_r2, `users`.`crypted_password` AS t1_r3, `users`.`password_salt` AS t1_r4, `users`.`persistence_token` AS t1_r5, `users`.`created_at` AS t1_r6, `users`.`updated_at` AS t1_r7, `users`.`type` AS t1_r8, `users`.`first_name` AS t1_r9, `users`.`last_name` AS t1_r10, `users`.`address` AS t1_r11, `users`.`zip_code` AS t1_r12, `users`.`city` AS t1_r13, `users`.`country` AS t1_r14, `users`.`mobile` AS t1_r15, `users`.`company_name` AS t1_r16, `users`.`vat_number` AS t1_r17, `users`.`terms_and_conditions` AS t1_r18, `users`.`show_in_dictionary` AS t1_r19, `users`.`lat` AS t1_r20, `users`.`lng` AS t1_r21, `users`.`show_mobile` AS t1_r22, `users`.`single_access_token` AS t1_r23, `users`.`newsletter` AS t1_r24, `users`.`real_range` AS t1_r25, `users`.`extend_range_till` AS t1_r26, `users`.`score` AS t1_r27, `users`.`description` AS t1_r28, `users`.`url` AS t1_r29, `users`.`avatar_file_name` AS t1_r30, `users`.`avatar_content_type` AS t1_r31, `users`.`avatar_file_size` AS t1_r32, `users`.`avatar_updated_at` AS t1_r33, `users`.`current_login_at` AS t1_r34, `users`.`last_login_at` AS t1_r35, `users`.`locale` AS t1_r36, `users`.`vat_requested` AS t1_r37, `users`.`is_legal` AS t1_r38, `users`.`state_id` AS t1_r39, `users`.`fod_registered` AS t1_r40, `users`.`fod_registration_number` AS t1_r41, `users`.`fod_last_updated_at` AS t1_r42, `users`.`fod_last_checked_at` AS t1_r43, `fields`.`id` AS t2_r0, `fields`.`name_nl` AS t2_r1, `fields`.`name_fr` AS t2_r2, `fields`.`name_en` AS t2_r3, `fields`.`created_at` AS t2_r4, `fields`.`updated_at` AS t2_r5, `fields`.`jobs_count` AS t2_r6, `fields`.`aliases_nl` AS t2_r7, `fields`.`aliases_fr` AS t2_r8, `fields`.`aliases_en` AS t2_r9, `price_types`.`id` AS t3_r0, `price_types`.`name_nl` AS t3_r1, `price_types`.`position` AS t3_r2, `price_types`.`created_at` AS t3_r3, `price_types`.`updated_at` AS t3_r4, `price_types`.`name_fr` AS t3_r5, `price_types`.`name_en` AS t3_r6, `price_types`.`short_code` AS t3_r7 FROM `jobs` LEFT OUTER JOIN `users` ON `users`.id = `jobs`.consumer_id AND (`users`.`type` = 'Consumer' ) LEFT OUTER JOIN `fields` ON `fields`.id = `jobs`.field_id LEFT OUTER JOIN `price_types` ON `price_types`.id = `jobs`.price_type_id INNER JOIN `users` ON `users`.id = `jobs`.consumer_id AND (`users`.`type` = 'Consumer' ) WHERE (((`users`.locale = 'nl') AND (( like '%v.c.s.%') OR (jobs.description like '%v.c.s.%') OR (users.zip_code like '%v.c.s.%') OR ( like '%v.c.s.%'))) AND (active = 1)) ORDER BY auction_ends_at <= '2010-02-26 09:26', `jobs`.updated_for_search_at DESC LIMIT 0, 8

Currently I impletmented a small "hack" in the named scope, which would replace a . with a \.;

{ :word => "%#{word.gsub('.', '\.')}%"}

Has anyone got any ideas on what may cause this behaviour?

Is it worth making a ticket in rails core lighthouse?


Hi, I had the same problem. The problem is that the AR code tries to deduce which tables are referenced in the query by looking at what is before a dot. I created a patch for this, but it was not accepted as it would have been too difficult to make sure the patch works reliably on all kinds of database.


first of all I think that this type of question is more suited for rubyonrails-talk group, no offense.

And I would suggest looking into sanitize_sql_for_conditions(condition, table_name = quoted_table_name) for your peoblem