2
\$\begingroup\$

When creating a new model in Rails, I need to pass a bunch of other models into the view so I can define the correct associations. All I’m really doing here is querying the persistence layer for models and assigning them to instance variables, but I’m having to do it twice. What’s the best way of extracting the assignment? Just creating a controller method and calling it? I’m not sure.

class ProjectsController < ApplicationController
  def new
    @project = Project.new
    @companies = Company.as_select_options
    @project_varieties = ProjectVariety.as_select_options
    @prices = Price.as_select_options
    @users = User.as_select_options
  end

  def create
    @project = Project.new(project_params)
    @companies = Company.as_select_options
    @project_varieties = ProjectVariety.as_select_options
    @prices = Price.as_select_options
    @users = User.as_select_options
    if @project.save
      flash[:success] = "#{@project.name} has been created."
      redirect_to @project
    else
      render :new
    end
  end
end
\$\endgroup\$
3
\$\begingroup\$

Simplest thing would be to add a protected method to the controller and a matching before_filter/before_action callback:

Your actions become:

class ProjectsController < ApplicationController
  before_filter :load_select_options, only: [:new, :create]

  def new
    @project = Project.new
  end

  def create
    @project = Project.new(project_params)
    if @project.save
      flash[:success] = "#{@project.name} has been created."
      redirect_to @project
    else
      render :new
    end
  end

  protected

  def load_select_options
    @companies = Company.as_select_options
    @project_varieties = ProjectVariety.as_select_options
    @prices = Price.as_select_options
    @users = User.as_select_options
  end
end

If you have an edit action, add that to the before-filter's list too.

However, since you (presumably) only need to load all that stuff in create if you have to render the new template, you can skip the before filter, and instead do something more targeted:

class ProjectsController < ApplicationController
  def new
    @project = Project.new
    load_select_options
  end

  def create
    @project = Project.new(project_params)
    if @project.save
      flash[:success] = "#{@project.name} has been created."
      redirect_to @project
    else
      load_select_options
      render :new
    end
  end

  protected

  def load_select_options
    @companies = Company.as_select_options
    @project_varieties = ProjectVariety.as_select_options
    @prices = Price.as_select_options
    @users = User.as_select_options
  end
end
\$\endgroup\$
3
\$\begingroup\$

I personally think that assigning that many instance variables for a view is a bit wrong - one other option is to create an PORO class to represent the role.

class SelectionOptions
  def company_select_options
    Company.as_select_options
  end

  def project_variety_options
    ProjectVariety.as_select_options
  end

  # etc...
end

This is then assigned in the controller (copied from other answer).

class ProjectsController < ApplicationController
  before_filter :set_select_options, only: [:new, :create]

  def new
    @project = Project.new
  end

  def create
    @project = Project.new(project_params)
    if @project.save
      flash[:success] = "#{@project.name} has been created."
      redirect_to @project
    else
      render :new
    end
  end

  protected

  def set_select_options
    @select_options = SelectOptions.new
  end
end

And then used in the view, however your view is coded.

<%= options_for_select @select_options.company_select_options %>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.