Problema con ActiveRecord

Ante el mal resultado que nos da Sphinx, que no permite buscar trozos de
palabras ni incluir la @ en las búsquedas, estoy tratando de hacer
búsquedas directamente sobre la BBDD. Pero tengo un problema con
ActiveRecord, y es que se comporta diferente en función de la palabra
que busco… Éste es el código:

  orden = 2
  condicion = "email like '%#{q}%' OR nick like '%#{q}%' OR nombre

like ‘%#{q}%’ OR apellidos like ‘%#{q}%’"
nusr = Usuario.count( :conditions => condicion )
@usuarios = Usuario.paginate( :per_page => 25, :page => @pagina,
:order => orden, :total_entries => nusr,
:select => “usuarios.puntos,
usuarios.posicion_ranking, usuarios.nick, usuarios.num_fans,
usuarios.nick_limpio, usuarios.id”,
:include => :estadisticas, :conditions => condicion )

Si busco @rankia, me va bien:

Processing UsuarioController#ranking (for 127.0.0.1 at 2008-09-18
14:48:40) [GET]
Parameters: {“commit”=>“Buscar”, “action”=>“ranking”, “q”=>“@rankia”,
“controller”=>“usuario”}

[4;36;1mSQL (0.136514) [0m [0;1mSELECT count(*) AS count_all FROM
usuarios WHERE ((email like ‘%@rankia%’ OR nick like ‘%@rankia%’ OR
nombre like ‘%@rankia%’ OR apellidos like ‘%@rankia%’)) [0m

[4;35;1mUsuario Load (0.250714) [0m [0mSELECT usuarios.puntos,
usuarios.posicion_ranking, usuarios.nick, usuarios.num_fans,
usuarios.nick_limpio, usuarios.id FROM usuarios WHERE ((email like
‘%@rankia%’ OR nick like ‘%@rankia%’ OR nombre like ‘%@rankia%’ OR
apellidos like ‘%@rankia%’)) ORDER BY 2 LIMIT 0, 25 [0m

[4;36;1mEstadistica Load (0.021374) [0m [0;1mSELECT
estadisticas.* FROM estadisticas WHERE (estadisticas.usuario_id IN
(203,18170,18198,18369)) [0m

[4;35;1mEstadistica Columns (0.022831) [0m [0mSHOW FIELDS FROM
estadisticas [0m

Completed in 0.89611 (1 reqs/sec) | Rendering: 0.02171 (2%) | DB:
0.79735 (88%) | 200 OK
[http://localhost/usuarios/ranking/total?q=%40rankia&commit=Buscar]

Pero si busco @rankia.com , me cambia la estructura de SQLs que genera,
provocando un error:

Processing UsuarioController#ranking (for 127.0.0.1 at 2008-09-18
14:48:46) [GET]

Parameters: {“commit”=>“Buscar”, “action”=>“ranking”,
“q”=>“@rankia.com”, “controller”=>“usuario”}

[4;36;1mSQL (0.180195) [0m [0;1mSELECT count(*) AS count_all FROM
usuarios WHERE ((email like ‘[email protected]%’ OR nick like
[email protected]%’ OR nombre like ‘[email protected]%’ OR apellidos like
[email protected]%’)) [0m

[4;35;1mEstadistica Columns (0.018872) [0m [0mSHOW FIELDS FROM
estadisticas [0m

[4;36;1mUsuario Load IDs For Limited Eager Loading (0.000000) [0m
[0;1mMysql::Error: #42S22Unknown column ‘2’ in ‘order clause’: SELECT
DISTINCT usuarios.id FROM usuarios WHERE ((email like
[email protected]%’ OR nick like ‘[email protected]%’ OR nombre like
[email protected]%’ OR apellidos like ‘[email protected]%’)) ORDER BY 2 LIMIT 0,
25 [0m

ActiveRecord::StatementInvalid (Mysql::Error: #42S22Unknown column ‘2’
in ‘order clause’: SELECT DISTINCT usuarios.id FROM usuarios
WHERE ((email like ‘[email protected]%’ OR nick like ‘[email protected]%’ OR
nombre like ‘[email protected]%’ OR apellidos like ‘[email protected]%’)) ORDER
BY 2 LIMIT 0, 25)

Como veis, si meto un punto en la búsqueda me hace un Limited Eager
Loading, PERO CON EL ORDER BY!! y esto da error… Por qué pasa esto?
Como puedo solucionarlo???

s2

Aquí se lee mejor:
http://pastie.org/private/bg8hhh01vsth8idsgxojw

Yep, al igual que escapas con h() en las vistas, en una query con
Sphinx has the poner una barra delante de "@"s.

El cliente PHP tiene escapeString():

Documentation | Sphinx

pero Thinking Sphinx en particular no la tiene implementada.

Hola,

Ante el mal resultado que nos da Sphinx, que no permite buscar trozos de
palabras

sí lo permite… puedes utilizar el carácter * para buscar fragmentos de
una palabra. Por defecto te busca palabras completas, pero usando el
comodín puedes buscar lo que quieras

ni incluir la @ en las búsquedas, estoy tratando de hacer búsquedas directamente sobre la BBDD

lo de la @ es más complicado porque forma parte de la sintáxis avanzada
de sphinx, donde @ representa un atributo, de forma que puedes hacer
cosas como
@title=loquesea

para que te busque solamente en ese campo.

En los foros de sphinx dan alguna solución para saltarse lo de la @. No
puedo decirte cuáles son las buenas porque en mi caso no lo tengo en
cuenta, pero quizás una forma sencilla sea dar el cambiazo en tu cadena
de búsqueda y sustituir la @ por . Así si alguien busca
[email protected] realmente estarás buscando jramirez
aspgems.com.
Para un gran número de casos creo que el resultado será el esperado.

saludos,

j


javier ramírez

…i do ruby on rails development in madrid, spain, at
http://www.aspgems.com
…you can find out more about me on http://formatinternet.wordpress.com
and http://workingwithrails.com/person/5987-javier-ramirez

O sea, que las propuestas van en el sentido de parchear el Sphinx, ¿no?
La verdad es que parece buena idea… de hecho, bastaría con el
gsub("@", “*”), no se necesita más.

Pero aunque eso me sirve… me quedo con la duda de por qué ActiveRecord
se comporta diferente en función de la palabra que busco, provocando
bugs si se mete un punto (.), igual es un fallo de Rails…

s2

2008/9/22 Fernando C. [email protected]:

O sea, que las propuestas van en el sentido de parchear el Sphinx, ¿no?
La verdad es que parece buena idea… de hecho, bastaría con el
gsub(“@”, “*”), no se necesita más.

No, no.

El modo de trabajo por defecto de Sphinx (y Thinking Sphinx) no tiene
ningun caracter especial. No hay que escapar nada. Si buscas por
[email protected]” y hay un tipo con ese email indexado saldra sin
mas. Hay 3 modos de trabajo asi.

Si estas trabajando en modo booleano hay una serie de caracters
especiales, y si estas trabajando en modo extendido hay unos pocos
mas. Si estas trabajando en alguno de estos modos has de saberlo
porque has tenido que configurarlo asi a proposito.

En los dos ultimos casos, el plugin no puede escapar por ti porque a
priori no sabe si quieres una query en el lenguaje de queries o
quieres una cadena literal. Por ello eres tu quien sabe si necesita
escapar. Es el mismo escenario que h(), tu sabes cuando se necesita.
Aqui TS no ofrece la funcion de escape en su version actual
(escapeString en PHP para modo extendido) y usarias la que yo envié,
supuesto que sea correcta.

Pero aunque eso me sirve… me quedo con la duda de por qué ActiveRecord
se comporta diferente en función de la palabra que busco, provocando
bugs si se mete un punto (.), igual es un fallo de Rails…

Parece un bug pero no lo puedo reproducir… Puedes enviar las
relaciones y la query AR que haces?

Xavier N. wrote:

2008/9/22 Fernando C. [email protected]:

O sea, que las propuestas van en el sentido de parchear el Sphinx, ¿no?
La verdad es que parece buena idea… de hecho, bastaría con el
gsub(“@”, “*”), no se necesita más.

No, no.

El modo de trabajo por defecto de Sphinx (y Thinking Sphinx) no tiene
ningun caracter especial. No hay que escapar nada. Si buscas por
[email protected]” y hay un tipo con ese email indexado saldra sin
mas. Hay 3 modos de trabajo asi.

Si estas trabajando en modo booleano hay una serie de caracters
especiales, y si estas trabajando en modo extendido hay unos pocos
mas. Si estas trabajando en alguno de estos modos has de saberlo
porque has tenido que configurarlo asi a proposito.

En los dos ultimos casos, el plugin no puede escapar por ti porque a
priori no sabe si quieres una query en el lenguaje de queries o
quieres una cadena literal. Por ello eres tu quien sabe si necesita
escapar. Es el mismo escenario que h(), tu sabes cuando se necesita.
Aqui TS no ofrece la funcion de escape en su version actual
(escapeString en PHP para modo extendido) y usarias la que yo envié,
supuesto que sea correcta.

OK, gracias por la aclaración

Pero aunque eso me sirve… me quedo con la duda de por qué ActiveRecord
se comporta diferente en función de la palabra que busco, provocando
bugs si se mete un punto (.), igual es un fallo de Rails…

Parece un bug pero no lo puedo reproducir… Puedes enviar las
relaciones y la query AR que haces?

La query ya viene en el primer registro, y las relaciones son muy
simples:

class Estadistica < ActiveRecord::Base
belongs_to :usuario

class Usuario < ActiveRecord::Base
has_many :estadisticas

La tabla usuarios tiene un índice en posicion_ranking (el campo por el
que se ordena), y la tabla estadisticas tiene una foreign key hacia
usuarios en el campo usuario_id… vamos, todo muy normal. Pero lo
realmente raro es que se gerera una u otra SQL en función de si hay o no
un punto en el texto buscado… ¿qué mas le dará a Rails que se busque
un texto u otro??

s2

Vista la funcion C de abajo, yo creo que si estas usando el modo
extendido la funcion de escape seria esta:

def self.escape_for_sphinx(str)
str.gsub(%r{(?=[()|!@~"&/\-])}, “\”)
end

En el caso de Thinking Sphinx solo se usa ese modo si haces la query
con :conditions => HASH para hacer una query por campos, o bien que tu
a mano lo hayas configurado.

– fxn

/* {{{ proto string SphinxClient::escapeString(string data) */
static PHP_METHOD(SphinxClient, escapeString)
{
char *str, *new_str, *source, *target;
int str_len, new_str_len, i;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",

&str, &str_len) == FAILURE) {
return;
}

    if (!str_len) {
            RETURN_EMPTY_STRING();
    }

    new_str = safe_emalloc(2, str_len, 1);
    target = new_str;
    source = str;
    for (i = 0; i < str_len; i++) {
            switch (*source) {
                    case '(':
                    case ')':
                    case '|':
                    case '-':
                    case '!':
                    case '@':
                    case '~':
                    case '"':
                    case '&':
                    case '/':
                    case '\\':
                            *target++ = '\\';
                            *target++ = *source;
                            break;
                    default:
                            *target++ = *source;
                            break;
            }
            source++;
    }
    *target = '\0';

    new_str_len = target - new_str;
    new_str = erealloc(new_str, new_str_len + 1);
    RETURN_STRINGL(new_str, new_str_len, 0);

}
/* }}} */