Reputation: 2362
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
And try to create something like this:
Controller
def create
user_service.display
end
private
def user_service
RenderService.new(self)
end
Separate Service object
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
Form for creating new user
= 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'
As you can see all I tried to do is to encapsulate logic into a separate class, but I got an error from ActionView::Template::Error
for some reason and the error says undefined method 'model_name' for nil:NilClass
.
I really don't understand why it does not work. To me it looks like I sent the same message to the same object and it should work just fine, but it doesn't.
Second question: why is ActionView
involved here?
Thank you for any explanation.
Oh, by the way, if you know the code responsible for such behavior and where it lives in the Rails repository please point me to it.
Thanks in advance. :)
Upvotes: 1
Views: 99
Reputation: 37607
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.
Upvotes: 2