Почему поведение Model.find в развернутой среде отличается от поведения в среде разработки? - PullRequest
1 голос
/ 06 февраля 2010

Используя Ruby on Rails в сочетании с capistrano и git, я столкнулся с досадной проблемой ..

У меня есть контроллер "people" с действием index, который выглядит примерно так:

def index
  @people = Person.find( :conditions => params[:search] )
end

В таблице Person есть логический столбец "is_admin". Если предположить, что некоторые люди являются администраторами, а некоторые нет, то вызов get http://localhost:3000/people?search[is_admin]=true должен заполнить @ people некоторыми пользователями ... И это верно для моего локального компьютера когда я запускаю приложение в режиме development ..

Но .... При развертывании на мою учетную запись сервера (railsplayground) при вызове http://mydomain.com/people?search[is_admin]=true не удается найти какие-либо подходящие элементы. Однако, если я изменю ...? Search [is_admin] = true на ...? Search [is_admin] = 1 ответ вернет пользователей-администраторов, как и ожидалось ...

Вернувшись на мой локальный компьютер, использование «1» вместо «true» не выполняется.

Суть в том, что

Person.find( :all, :conditions => { :is_admin => 'true' } )

работает в моей среде разработки, а

Person.find( :all, :conditions => { :is_admin => 1 } )

работает в моей развернутой среде.

Почему это? и как я могу это исправить?

В идеале я хотел бы разместить ссылки вроде:

link_to( "Administrators", {
  :controller => '/people',
  :action => :index,
  :search => { :is_admin => true }
})

и получите списки администраторов:).

Стоит отметить, что моя база данных для разработки - это файл sqlite3, а продукция - это база данных mysql ...

РЕДАКТИРОВАТЬ: Я понимаю возражения против ввода пользователя, но в этом исключительном случае это очень очень незначительная угроза. Кроме того, мой текущий код отлично работает либо на моем компьютере разработчика, либо на моей учетной записи, но не на обоих. Кажется, что самое простое решение меняет способ, которым sqlite3 сохраняет и интерпретирует логические значения, поэтому я бы изменил свой вопрос на «Как изменить способ, которым sqlite сохраняет логические значения» ... Если sqlite идеально имитирует поведение mysql, это послужит моим разработкам нужно отлично ...

Ответы [ 3 ]

3 голосов
/ 06 февраля 2010

Проблема в том, что вы передаете логическое значение в виде строки, и окончательное поведение зависит от активной базы данных. Вот более подробное объяснение.

Когда вы читаете переменную params[:search], содержимое является строкой и не имеет типа. Это потому, что строка запроса не может понять, является ли

params[:search][:is_admin] = "true"

на самом деле означает

params[:search][:is_admin] = "true"
params[:search][:is_admin] = true

Аналогично, когда вы проходите 1, вы в итоге получаете

params[:search][:is_admin] = "1"

отличается от

params[:search][:is_admin] = 1

Когда вы передаете значение в запрос, поскольку вы не передаете логическое значение, значение не переводится адаптером базы данных. Ваш окончательный результат запроса будет выглядеть примерно так:

SELECT * FROM `persons` WHERE `persons.is_admin` = 'true'

SQLite3 сохраняет логические значения в виде строк t / f. true сохраняется как 't', а false сохраняется как 'f'. Я думаю, он также понимает истину / ложь и автоматически переводит ваш запрос. Напротив, MySQL понимает только 0/1 и true / false, и это терпит неудачу.

Когда вы переключаете поведение, передавая 1 вместо true, вы вызываете сбой SQLite, поскольку он не может перевести строку "1" в 't'. Напротив, MySQL может и делает.

В обоих случаях вы делаете это неправильно. Вы не должны передавать строку, а логическое значение. Кроме того, вы никогда не должны доверять пользовательскому вводу.

Я предлагаю нормализовать ваш params[:search] перед кормлением Person.find.

2 голосов
/ 06 февраля 2010

Я думаю, что причиной вашей проблемы является разница между использованием sqlite и mysql.

Но разрешить передачу активной записи, обусловленной непосредственно из строки запроса, мне кажется плохой идеей и может оставить ваше приложение открытым для атак SQL-инъекцией.

0 голосов
/ 06 февраля 2010

@ Симона ответ объясняет, почему вы получаете такое поведение. Чтобы получить правильный результат как в sqlite3, так и в mysql, вы должны передать:

Person.find( :all, :conditions => { :is_admin => true } )

Если у вас будет только один уровень параметров (у вас есть params[:search][:something], но ничего глубже), чем вы можете создать правильный хеш с такими условиями:

my_conditions = Hash.new
params[:search].each do |item, value|
  my_conditions[item.to_sym] = value == 'true' ? true : value == 'false' ? false : value
end

Он будет перебирать все параметры поиска и изменит все 'true' на true и 'false' на false. Если будет другое значение, оно останется без изменений. Конечно, вы можете поставить здесь любую логику, какую захотите. Если это будет сложнее, чем вы можете переписать его с помощью if или switch.

Чем вы можете:

Person.all(:conditions => my_conditions)
...