KhoaVo KhoaVo - 7 months ago 15
Ruby Question

would it be ok to create an instantiate an object in another controller?

I have a controller for customer. In the new action, I redirect to another page, which belong to the pages controller

class CustomersController < ApplicationController
def new
redirect_to register_path
end


Would it be possible to create the object in the registration action like this?

class PagesController < ApplicationController
def registration
@customer = Customer.new
end
end

Answer

I believe the setup is something like this: you have models in your application for a Customer and, say, an Agent. Your website users register as either and the desire is to have a single HTML page (URL) with both options available. They choose which one they are and submit some fields, say name/email/password. To keep it simple, without bothering with JavaScript to hide things behind tabs, you have something like:

**Customer**
Name: ___________
Email: __________
Password: _______
[Submit]

**Agent**
Name: ___________
Email: __________
Password: _______
[Submit]

You have a few options here to avoid your guilty feeling in the Rails controllers:

  1. Go heavy client-side JavaScript. Don't have the new actions on the controllers. The JavaScript creates the page elements. The create action becomes a JSON API endpoint, thereby avoiding the problem in the Ruby application. This is obviously a significant architectural deviation from where I think you are today.
  2. Use a little bit of JavaScript to dynamically load the correct 'partial' into the DOM when the user switches between the options. Avoids the underlying problem in the question by effectively separating the 'pages' out to the two controllers. The Pages→registration action does not need to set any instance variables for the view. The JavaScript deals with the partial loading. (see 'link_to' and the 'remote' option)
  3. Don't include both forms in the same HTML page, just default to one, say the Customer one, and provide a link to navigate to the Agent one, e.g. a link in a tab, or a plain link like "Not a Customer? Register as an Agent." In this scenario, you have a neat mapping to the Ruby MVC design, each of the pages are just the new action of its relevant controller. The downside is a page load to change between the two options. This is the simplest, plainest choice … if you can get the boss to agree to the UX. PS: if you are using turbolinks, then the 'feel' of this option in the browser will be not far from option (2).
  4. Stick to your design

Keep in mind that you will have difficultly dealing with error conditions and messages with option (4). You can do it, but the code won't be simple or easy to maintain.

If option (4) is a must, one simplification can be the create actions on each of the controllers rendering their own new in case of an error. If you submit the 'Agent' form from your starting page, with errors, to the Agents→create action, that action finishes with a render 'new' to show the user the Agents→new page. No 'customer' form is visible. You could then add a sprinkle of option (3) in there with a "Not an Agent? Register as a Customer." link under the form. Doing this greatly simplifies your error handling.

Which then leads to a suggestion for your original problem. Cheat. Don't have an @customer instance variable for the new actions (or the registration action). Use partials for the customer and agent forms, and pass in a new object to form_for, e.g.


pages/registration.html.erb

<%= render 'customers/new_form' %>
<%= render 'agents/new_form' %>

customers/new.html.erb

<%= render 'customers/new_form' %>

customers/_new_form.html.erb

<% form_for Customer.new do |f| %>
  <%# include the inputs shared with the edit action %>
  <%= render 'fields', f %> 
  <%= f.submit %>
<% end %>

customers/_fields.html.erb

<%# 'f' is one of the locals passed to the partial %>
<% f.input_field :name %>
<% f.email_field :email %>
<% f.password_field :password %>

customers/edit.html.erb

<% form_form @customer do |f| %>
  <%= render 'fields', f %>
  <%= f.submit %>
<% end %>

… then you would follow the same pattern for:

  • agents/new.html.erb
  • agents/_new_form.html.erb
  • agents/_fields.html.erb
  • agents/edit.html.erb