Почему модель.поведение поиска в развернутой среде отличается от поведения в среде разработки?
-
19-09-2019 - |
Вопрос
Используя Ruby on Rails в сочетании с capistrano и git, я столкнулся с досадной проблемой..
У меня есть контроллер "people" с индексным действием, которое выглядит примерно так:
def index
@people = Person.find( :conditions => params[:search] )
end
В таблице Person есть логический столбец "is_admin".Предполагая, что некоторые люди являются администраторами, а некоторые нет, вызов get для http://localhost:3000/people ?поиск[is_admin]=истина следует заполнить @люди с некоторыми пользователями...И это верно для моего локального компьютера, когда я запускаю приложение в развитие режим..
Но....Когда я выполняю развертывание в своей учетной записи сервера ( railsplayground ), вызов http://mydomain.com/people ?поиск[is_admin]=истина не удается найти ни одного подходящего элемента.Однако, если я изменю ...?поиск[is_admin]=true Для ...?поиск[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, а production - это база данных mysql...
Редактировать:Я понимаю возражения против пользовательского ввода, но в данном исключительном случае это очень, очень незначительная угроза.Кроме того, мой текущий код отлично работает либо на моем компьютере разработки, либо в моей производственной учетной записи, но не на обоих.Кажется, самое простое решение изменить способ сохранения и интерполяции логических значений sqlite3, поэтому я бы изменил свой вопрос на "как мне изменить способ сохранения логических значений sqlite"...Если бы sqlite идеально имитировал поведение mysql, это идеально соответствовало бы потребностям моих разработок...
Решение
Проблема в том, что вы передаете логическое значение в виде строки, и окончательное поведение зависит от активной базы данных.Вот более подробное объяснение.
Когда вы читаете 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'
.Я предполагаю, что он также понимает значение true / false и автоматически переводит ваш запрос.Напротив, MySQL понимает только 0/1 и true / false, и это приводит к сбою.
Когда вы переключаете поведение, передавая 1
вместо того , чтобы true
, вы вызываете сбой SQLite, потому что он не может перевести строку "1"
Для 't'
.Напротив, MySQL может, и это делает.
В обоих случаях вы делаете это неправильно.Вы должны передавать не строку, а логическое значение.Кроме того, вы никогда не должны доверять пользовательскому вводу.
Мое предложение состоит в том, чтобы нормализовать ваш params[:search]
перед кормлением Person.find
.
Другие советы
Я думаю, что разница между использованием sqlite и mysql является причиной вашей проблемы.
Но разрешение передавать active record conditioned непосредственно из строки запроса кажется мне плохой идеей и может оставить ваше приложение открытым для атак с использованием sql-инъекций.
Ответ @Simone объясняет, почему у вас такое поведение.Чтобы получить правильный результат как в 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)