Подпишитесь на рассылку о самых интересных материалах в мире веб-разработки :)

Валидации Rails


#1

Вопрос не касается проверок js или HTML5; предположим, поставлена задача валидировать значения формы именно средствами rails, модель же приложения при этом не предусматривает записи в db.

Соответственно, поступаю следующим образом: убираю из класса модели наследование от ApplicationRecord (или ActiveRecord::Base для старого rails) и инклюдю ActiveModel::Model, пишу там же attr_accessor и собственно валидации.
Ну и в контроллере меняю save на valid, как-то например так:

def new
    @form = Form.new
end
 
def create
    @form = Form.new(form_params)
    respond_to do |format|
        if @form.valid?
        ------------------
        blahblahblah

Вроде пашет.

  1. А вопрос в следующем. Может быть, есть какой-то более красивый способ использования валидаций для тех нередких случаев, когда нет нужды записи в db? Или, может, готовый гем…? - было бы идеально, фреймворк все-таки, хотелось бы удобств. ))

  2. ДОпустимо ли каким-нибудь трюком вообще обойтись без модели при использовании валидаций? - только контроллер и вьюха если? Покажите, если кто знает. Пусть изврат, тем более интересно. :rofl:

Спрашиваю в контексте любых форм, как статичных, так и сгенеренных хелперами rails или simple_form.


(Kvokka) #2

канеш можно. можно вообще на MVC положить, ведь это лишь соглашение, чтобы удобнее было потом код саппортить.
при этом, возвращаясь у твоему примеру, я не вижу смысла в явной проверке валидации

вот тебе пища для размышлений

  def form() @form ||= Form.new(form_params);end # <= тут тоже надо тебе красивые хелперы пилить

  def create
    form.save!
    respond_to { |f| f.html { render :good } }
  rescue ActiveModel::StrictValidationFailed
    respond_to { |f| f.html { render :bad } }
  end

tell, don’t ask


#3

dry-validation


(Kvokka) #4

с десяток зависимостей + если что-то пошло не так гугление не поможет + прирост на валидном объекте 10% (и то, что это получить надо все скачать, авторам было это как-то не с руки в камментах запилить) + для рельсового приложения странно + когда джуны набегут на проект будет весело + когда саппорт этого гема сдохнет то все ровно будет AM == надо брать

ну и как вишенка на торте, вопрос был не про то, инструмент, что выполняет валидацию, а как ее описать в контроллере.


#5

так говорите, как будто его никто не юзает + сорцы в помощь

Джунам лучше сразу объяснить, что модель не должна отвечать за валидацию, валидация должна быть в form object.

А Вы вообще гемы не юзаете, да?

Ну и как вишенка, в вопросе также спрашивали про гем:


(Kvokka) #6

вопрос был про то, как организовать код в контроллере, а не то, как избавиться или перепилить валидации.

в сравнении с ActiveModel он заочно смотрится менее плюсово ввиду обширности использования. в сравнении с ней все смотрится менее плюсово :wink:

идея form_object не нова и не плоха. только вот это далеко не панацея.
и несколько не верно навязывать идею их применения для любого и каждого.


#7

Спс что накидали вариаций, пробую и так и эдак, почему и не отвечал… являюсь приверженцем того научного метода, в силу которого у подопытного должен быть выбор, а не как у рогозинского таксы Николаса. Чтоб уж два раза машину не гонять, раскопал заодно hanami-validations и еще несколько связанных тем, что собсно и требовалось. Копаюсь. Имхо, на определенном этапе такой подход способен заменить тонны литературы; впрочем, зависит от темперамента канеш. ))

Ну и про джунов. “Прошу извинить, но предмет вашей ученой беседы настолько интересен, что…” Некоторое время назад мой приятель, классный devops, подвизавшийся ранее в звучной американской компании и перешедший теперь на Яндекс (да, @kvokka , бывает и такое в нашем безумном мире), задвинул мне следующий тезис: рельсы безнадежно мертвы и смысла в них сегодня реально по нолям, в качестве аргумента же прозвучала грустная история о том, что вот дескать ломанулись в прошлом году у него в кампашке индусы обновлять знаковую аппликуху, ну и один из гемов в депенденциях какую-то х**ню - сюрпрайз-сюрпрайз - и вытянул, вследствие чего индусы-то соскочили, в силу толерантного к ним отношения, а вот нашим, как всегда, влетело.

Это я привел в качестве примера вектор рассуждений джуна, господа-академики. А вы тут про MVC толкуете. :yum:


(Kvokka) #8

ну, что непосредственно уместнее запилить явно тебе виднее.

про мертвые рельсы я уже столько раз слышал, что не пересказать. но, при этом, за них все ровно норм платят, да и не вижу я проблемы великой перескочить на другой язык. рельса- прекрасный фреймворк (в частности для руби), чтобы на нем экспериментировать с разными паттернами и учиться на уже готовых примерах рельсы как можно делать весьма лаконичные интерфейсы. понятное дело, что по скорости это все далеко не топ. и что потом придется учить другой язык. но фундамент на руби и рельсах ставить комфортно


#9

Еще пример, иллюстрирующий на этот раз получение с weather station погоды по id населенного пункта, вводимого посредством формы. Примерно вот так написал… Action суть класс Weather , содержащий в себе ряд методов… и валидации ActiveModel , прописанные в модели, вкупе с флеш-мессаджами на этот раз полностью реализуют “защиту от дурака”, т.е. неправильного инпута.

Что-нибудь, по логике или форме, критично неоптимально?

class FormController < ApplicationController
	include Action
	def weather
		@array = []
		if params[:request] != nil
			@weather = Form.new(form_params)
            if @weather.valid?
			@array = @array << params[:request]
			@cities = @array.join(",")	
            @lookup = Weather.call(@cities)
            if Weather.call(@cities).empty?
            flash[:error] ||= [] 
            flash[:error] << 'Error. Invalid ID: ' + params[:request] + '. Data not received. Server response: ' + response.status.to_s
			else
            flash[:notice] ||= [] 
            flash[:notice] << 'Success. ID: ' + params[:request] + '. Server response: ' + response.status.to_s	
            end
			end
		end
	end  
	private
	def form_params
		params.permit(:request)
	end
end

(Kvokka) #10

я тебе в несколько мест ткну, а дальше уже сам. но плохо. весьма.
поехали.
params[:request] врятли когда-то будет именно false, зачем проверять на nil?
у params есть метод require, неплохо бы его заюзать
переменная @cities не объявлена для пустого params[:request]. не похоже, чтобы это было так и задумано, ну или потом аукнется
количество if зашкаливает, почитай про early return
Weather.call(@cities) вызывается дважды. это пичалька
имя переменной @array эпично
ну и тд.


#11

госссподи, это-то при чем?! :joy:
“как вы яхту назовете, так она и поплывет”?


(Kvokka) #12

инстанс переменная, которая не нужна даже в теле этого метода, не то, чтобы далее

  • ее имя говорит о ней приблизительно ничего

#13

@kvokka , или я реально не врубаюсь, или ты на своем пятничном попкорне тормозишь. Что значит врятли? когда открываешь страничку с пустой формой, именно так оно и будет:

params[:request].inspect => nil


(Kvokka) #14

О_о
ну так ты только что и подтвердил мои слова. false != nil и при этом и то и то falsey
так нафига проверять что это именно nil?

при этом же обычно для params есть метод #require куда уже, по твоей семантике, и должен переть :request


#15

ну да, ну да… можно было проще и лаконичнее, конечно… взгляни, ну проще некуда уже. Нет разве? Функционал один в один тот же самый.

Только я не врубаюсь все одно, нафига здесь require … и можешь показать, как адекватно заменить здесь elseif на return unless ? я попробовал, в соответствии с докой, че-то не проходит оно…

class FormController < ApplicationController
	include Action
	def weather
		@weather = Form.new(form_params)
		if @weather.valid?
						@lookup = Weather.call([params[:request]].join(","))
						if @lookup.empty?
				flash[:error] ||= [] 
				flash[:error] << 'Error. Invalid ID: ' + params[:request] + '. Data not received. Server response: ' + response.status.to_s
			else
				flash[:notice] ||= [] 
				flash[:notice] << 'Success. ID: ' + params[:request] + '. Server response: ' + response.status.to_s
            end
		end    
	end  
        private
	def form_params
		params.permit(:request)
	end
end

(Kvokka) #16

сейчас если @weater.empty? # => @lookup = nil пмсм это не норм


(Kvokka) #17

ну и будет тогда

def weather
	@weather = Form.new(form_params)
    @lookup = Weather.call([params[:request]].join(","))  
	(flash[:notice] ||= []) << 'Success. ID: ' + params[:request] + '. Server response: ' + response.status.to_s
  rescue Weather::NoFoundError
    (flash[:error] ||= []) << 'Error. Invalid ID: ' + params[:request] + '. Data not received. Server response: ' + response.status.to_s
    render ...
  rescue Weather::UnvalidError
  # some crap
end

#18

не, ну так не получается:


ActionController::ParameterMissing in FormController#weather
param is missing or the value is empty: request

или

NameError in FormController#weather
uninitialized constant Action::Weather::NoFoundError

какой профит ожидается от использования rescue вместо elsif ?


(Kvokka) #19

ну так и надо чтобы проверял наличие параметра :request, судя по семантике. а что не так-то? это ведь ошибка, если его нет, по сути своей.

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

меньше кода == меньше херни


#20

По доке действительно так, как ты написал:

Но я вижу, например, что на SO очень нередко возникающие в силу strong parameters конфликты советуют решать похожим способом, попросту обойдясь без require. Или вот этот, например, экскурс.

Ты думаешь, что в данном случае имеет место потенциальная проблема безопасности? Мы ведь даже в db ничего не пишем. Уверен, что ошибка?