Почему бы не обратиться к хэшу сеанса на уровне модели?

Этот вопрос неоднократно задавали, и на него отвечали однострочными утверждениями, такими как «потому что это очевидное нарушение MVC». Честно говоря, я просто не понимаю. В самом деле, мне кажется, что размещение сеанса внутри контроллера просто артефакт, который ApplicationController сталкивается с сетевым уровнем через вызов стойки, а не мандат MVC. Позвольте мне объяснить мою проблему.

Запуская аутентификацию с нуля, я обнаружил, что мучаюсь и всплескиваю повсюду из-за отсутствия возможности сформулировать простые тесты (сессия также недоступна для среды тестирования). Моя схема аутентификации, как и почти все, что я видел в rails, хотела использовать хэш сеанса в качестве уровня сохраняемости, чтобы сохранить идентификатор для модели пользователя «текущего пользователя». Разве это не похоже НАМНОГО БОЛЬШЕ на модель, чем на артефакт контроллера?

Запахи кода очевидны, когда вы смотрите на «типичный» контроллер сеансов (этот из превосходных скринкастов Райана Бейтса). Отчаявшись перелопатить эту концепцию с отдыхом, мы видим нездоровый язык, такой как:

def create 
  user = User.find_by_email(params[:session][:email])
  if user && user.authenticate(params[:session][:password])
    session[:user_id] = user.id
    redirect_to root_url, notice: "Logged in!"
  else
    flash.now.alert = "Email or password is invalid"
    render "new"
  end
end

Для меня это запах кода, явно перегруженный контроллер, который требует рефакторинга! Но мы не можем. Почему? Ах да, потому что вставлять в модель ссылки на сессию, используемую в качестве юриста настойчивости, является нарушением MVC. ВТФ? Разве это не говорит вам о том, что мы, кажется, хотим НАЗВАТЬ ЭТОТ РЕСУРС REST /sessions?

Чтобы понять, почему это просто глупо, посмотрите на свои представления входа в систему — HTML-код, написанный вручную, или использование API «_tags»? Если бы у нас была модель ActiveModel для выполнения этого кода, то код создания мог бы выглядеть как обычные строительные леса или, возможно, даже сведен к однострочнику «respond_with».

def create 
  recognition = Recognition.new(params[:user])
  if recognition.save
    redirect_to root_url, :notice => "Thank you for signing up!"
  else
    render "new"
  end
end

Затем взгляните на закодированный вручную html для всех этих представлений! Если бы распознавание было моделью, сохраняемой сеансом (или каким-либо другим способом, который в любом случае не должен быть обязанностью уровня контроллера), то вы могли бы просто использовать построитель форм или simple_form для создания форм. Конечно, мы могли бы просто передать хэш сеанса методу распознавания класса «new_login», скажем, Recognition.on(session).new(params[:recognition]), но это кажется более уродливым, чем должно быть. Возможно, это неотъемлемо, так как мы захотим использовать ссылку current_user позже на уровне приложения, возможно, Recognition.on(session).current_user аналогично тому, как можно было бы использовать одноэлементный шаблон?

Просто попробуйте создать свой пакет аутентификации со строгим BDD и честно скажите мне, что вы не добавили эту часть? Если бы у нас была модель распознавания, все это сводилось бы к простому набору модульных тестов без хакерства. Теперь вместо этого у нас есть «единственный» вариант использования для интеграционного тестирования, волшебное вторжение в модули ActiveController и хаки для ускорения любого приемочного тестирования предиката logged_in_as.

Я думаю, что весь смысл ActiveModel заключался в том, чтобы облегчить такое переосмысление и рефакторинг. Не все модели используют базу данных "the". Почему бы не настаивать на "сессии"?

Я слишком долго пользуюсь devise и его аналогами, хороня эти запахи под предлогом «не связывайся с драгоценным камнем», чтобы мне не приходилось на них смотреть. Больше никогда! Я думаю, что с этого момента я буду отвергать фанатиков. Извините, для меня сеанс — это уровень сохраняемости, которым следует манипулировать на уровне модели MVC. Я утверждаю, но не уверен, что причина, по которой он живет в мире контроллеров, больше связана с уродливым или элегантным фактом, что контроллеры являются стоечными объектами, чем с какой-либо теоретической магией MVC.

Итак, еще раз, есть ли более элегантный способ доступа к сеансовому уровню, чем иметь для этого логику в контроллере?


person wizardwerdna    schedule 01.03.2012    source источник


Ответы (1)


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

Я думаю, что люди иногда доводят идею «тощих контроллеров» до нездоровой крайности. Да, вы хотите, чтобы все, что может быть в модели, было в модели: но существуют контроллеры для изменения моделей в зависимости от состояния приложения, и вы должны позволить им выполнять эту цель проектирования. Каждый контроллер должен быть чем-то вроде:

def create
  Creator.create(params) # Pass all params to the creator model, which will automatically detect what object you're trying to create, make it, and then render the appropriate view
end

def show
  Shower.show(params) # Find the model object and the view based on the params and render them together
end

Бросает вызов идее разделения обязанностей и создает кошмарные проблемы для людей, пытающихся обновлять и поддерживать ваш код. Контроллеры должны вызывать объекты модели и даже методы этих моделей для создания и сохранения состояния приложения, а модели не должны зависеть от состояния приложения. Если вы свяжете их слишком тесно, вы получите код представления и контроллера в своих моделях, после чего становится очень сложно определить, что находится где в вашем приложении.

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

Все это говорит о том, что модель сеанса, которую вы предлагаете, на самом деле довольно хорошая идея... и, следовательно, она уже существует. ;) Фреймворк authlogic имеет модель Sessions: при входе в систему с использованием authlogic вы создаете новый UserSession с объект параметров. UserSessions живут в папке моделей и существуют исключительно для того, чтобы абстрагироваться от мельчайших деталей аутентификации в класс модели с поддержкой контроллера, поэтому, если это то, что вы ищете, это уже сделано для вас! Посетите репозиторий authlogic github, чтобы найти документацию и примеры использования.

Тем не менее, я бы по-прежнему воздерживался от передачи любого состояния контроллера в реальную модель ActiveRecord. Разрешите вашим контроллерам манипулировать моделями и отображать результаты этих манипуляций в виде HTML — для этого они и нужны!

person Veraticus    schedule 01.03.2012
comment
Большое спасибо за замечания. Интересно, что Rails развился до описанных вами однострочников, хотя и не с моделями per_se. Механика контроллера действительно эволюционировала в 3.X, чтобы использовать response_with после вызова уровня модели. Мое предложенное изменение не сводит контроллер к модели, а сводит ненужную логику в контроллере к вызову модели ТОЧНО так же, как используется постоянное содержимое базы данных. - person wizardwerdna; 02.03.2012