I'm doing a small experiment and trying to extract some logic from controller's action.
I have code like this:
Controller
def create
@user = User.new(user_params)
if @user.save
redirect_to user_url(@user), notice: 'Welcome to MyApp'
else
render :new
end
end
def create
user_service.display
end
private
def user_service
RenderService.new(self)
end
require 'forwardable'
class RenderService
extend Forwardable
delegate [:params, :user_url, :redirect_to, :render] => :@obj
attr_reader :obj, :user
def initialize(obj)
@obj = obj
@user = User.new(user_params)
end
def display
redirect_to(user_url(user), notice: 'Welcome to MyApp') if user.save
render(:new) unless user.save
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :confirmation)
end
end
= simple_form_for(@user, html: { }) do |form|
= form.input :name, required: true
= form.input :email, required: true
= form.input :password, required: true
= form.input :confirmation, required: true
= form.submit 'Create my account', class: 'btn btn-primary'
ActionView::Template::Error
undefined method 'model_name' for nil:NilClass
ActionView
This happens because you're setting the @user
instance variable on the instance of RenderService
rather than on the controller instance (@obj
).
render
creates an instance of ActionView::Base
(self
in a template) and then copies the controller's instance variables to the view instance. Since @user
isn't set on the controller instance, it's never copied and no such variable is set in the view. The view calls model_name
on the nonexistent @user
instance variable and you get the error you saw.
You should be able to fix it by setting @user
on the controller instance. Instead of
@user = User.new(user_params)
do
@obj.instance_variable_set :@user, User.new(user_params)
It's difficult to fully describe what happens when a Rails controller renders a view, because there is a lot of subclassing and overridden methods, so if you really want to understand it I recommend stepping through a render
call in a debugger. Eventually you'll get to ActionView::Rendering#_render_template
and then #view_context
. There you'll see the call to #view_assigns
, where the controller tells the view what instance variables it has.