При использовании неглубоких маршрутов для разных маршрутов требуются разные аргументы form_for.

Здесь я использую Simple Form, но это проблема и с обычными формами Rails. При использовании неглубоких маршрутов form_for нужны разные аргументы в зависимости от того, в каком контексте он используется.

Пример: для редактирования (http://localhost:3000/notes/2/edit) файл _form.html.erb должен иметь simple_form_for(@note). Но для создания новой заметки (http://localhost:3000/customers/2/notes/new) _form.html.erb нужен simple_form_for([@customer, @note]). Если какой-либо из них получит неправильные аргументы, я получу ошибку «Метод не найден».

Как лучше всего справиться с этим?

  • Я мог бы сделать две отдельные формы, но это выглядит беспорядочно.
  • Я должен установить @customer для обратной ссылки, но я мог бы использовать другую переменную в форме (скажем, @customer_form) и просто не устанавливать ее в методах редактирования и обновления, но это непоследовательно и немного сбивает с толку, так как я должны установить как @customer_form, так и @customer в новом методе.
  • Я мог бы сделать то, что сделал этот парень, и разделить формируются в нескольких файлах. Пока это выглядит как лучший вариант, но мне он не очень нравится, так как вы не можете просто открыть _form.html.erb и посмотреть, что происходит.

Это мои единственные варианты?

Пример следующий:

config/routes.rb

Billing::Application.routes.draw do
  resources :customers, :shallow => true do
    resources :notes
  end
end

грабли маршрутов | заметка grep

    customer_notes GET    /customers/:customer_id/notes(.:format)         notes#index
                   POST   /customers/:customer_id/notes(.:format)         notes#create
 new_customer_note GET    /customers/:customer_id/notes/new(.:format)     notes#new
         edit_note GET    /notes/:id/edit(.:format)                       notes#edit
              note GET    /notes/:id(.:format)                            notes#show
                   PUT    /notes/:id(.:format)                            notes#update
                   DELETE /notes/:id(.:format)                            notes#destroy

app/views/notes/_form.html.erb

#                      v----------------------------- Right here
<%= simple_form_for (@note), html: { class: 'form-vertical'} do |f| %>
  <%= f.input :content %>

  <%= f.button :submit %>
<% end -%>

app/views/notes/new.html.erb

<h1>New note</h1>

<%= render 'form' %>

<%= link_to 'Back', customer_path(@customer) %>

app/views/notes/edit.html.erb

<h1>Editing note</h1>

<%= render 'form' %>

<%= link_to 'Show', @note %>
<%= link_to 'Back', customer_path(@customer) %>

приложение/контроллеры/notes_controller.rb

class NotesController < ApplicationController

def show
  @note = Note.find(params[:id])
  @customer = Customer.find(@note.customer_id) 

  respond_to do |format|
    format.html
    format.json {render json: @note }
  end
end

  # GET /notes/new
  # GET /notes/new.json
  def new
    @note = Note.new
    @customer = Customer.find(params[:customer_id])

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @note }
    end
  end

  # GET /notes/1/edit
  def edit
    @note = Note.find(params[:id])
    @customer = Customer.find(@note.customer_id)
  end

  # POST /notes
  # POST /notes.json
  def create
    @customer = Customer.find(params[:customer_id])
    @note = @customer.notes.build(params[:note])

    respond_to do |format|
      if @note.save
        format.html { redirect_to @customer, notice: 'Note was successfully created.' }
        format.json { render json: @note, status: :created, location: @note }
      else
        format.html { render action: "new" }
        format.json { render json: @note.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /notes/1
  # PUT /notes/1.json
  def update
    @note = Note.find(params[:id])
    @customer = Customer.find(@note.customer_id)

    respond_to do |format|
      if @note.update_attributes(params[:note])
        format.html { redirect_to @customer, notice: 'Note was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @note.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /notes/1
  # DELETE /notes/1.json
  def destroy
    @note = Note.find(params[:id])
    @note.destroy

    respond_to do |format|
      format.html { redirect_to :back }
      format.json { head :no_content }
    end
  end
end

person James    schedule 19.03.2012    source источник
comment
См. решение Эрика ниже. Решение так же просто, как не определять @customer в вашем действии редактирования.   -  person Andrew    schedule 10.07.2014


Ответы (3)


Если первый объект в массиве, который вы передаете построителю формы, будет nil, Rails выполнит POST только для второго объекта. По этой причине просто не устанавливайте свой @customer объект в действие редактирования вашего контроллера. Если вам нужен доступ к объекту клиента, вызовите его через @note.

Если вы используете один и тот же фрагмент для создания и редактирования, вам нужно установить @note.customer в новом действии контроллера (@customer не будет установлено при редактировании).

Я думаю, что именно так команда Rails планировала работать.

person Eric Boehs    schedule 21.08.2012
comment
Иногда решение настолько просто, что не очевидно. - person Andrew; 10.07.2014
comment
но как вы могли сделать @note.customer ?? Я попытался получить доступ к родительскому объекту(@customer) из дочернего(@note) с помощью @note.customer, но он вернул бы только нулевой класс... - person Sardonic; 10.02.2015
comment
@Sardonic В _form вам нужно будет выполнить form_for [@customer, @note], затем в NotesController установить @customer в своем new действии, но не edit. Если вам нужен доступ к клиенту, вы можете использовать @note.customer || @customer. - person Eric Boehs; 12.02.2015
comment
@Sardonic @note.customer будет nil за ваше new действие. Вам нужно будет найти и установить @customer на основе params[:customer_id]. Вы можете сделать что-то вроде @note = @customer.notes.new, чтобы установить @note.customer. Если вы находитесь в действии edit, а оно nil, что-то еще не так, что выходит за рамки этого вопроса (например, ваши ассоциации или данные таблицы испорчены). - person Eric Boehs; 13.02.2015
comment
Это также работает с пространствами имен, например. [:admin, @customer, @note], где @customer установлено на новой странице, но ноль на странице редактирования. - person nruth; 07.10.2015
comment
Это работает для меня, однако я не могу проверить это, так как это приводит к сбою в моих тестах из-за значения, равного нулю. - person android_student; 09.03.2016

Я хотел бы предложить небольшую модификацию решения Джеймса:

# app/helpers/application_helper.rb
def shallow_args(parent, child)
  child.try(:new_record?) ? [parent, child] : child
end

Вместо того, чтобы полагаться на то, что действие контроллера будет называться «новым» — хотя это, вероятно, будет в 95% случаев — это просто проверяет, является ли дочерний элемент новой записью.

person imderek    schedule 30.03.2012
comment
Я на самом деле столкнулся с этим немного назад и изменил проверку на params["#{parent.class.name.downcase}_id"].nil? == false, но это выглядит чище. - person James; 30.03.2012
comment
СПАСИБО! избавил меня от многих головных болей - person Ryan.lay; 21.01.2014
comment
См. решение Эрика ниже. Нет необходимости в специальном вспомогательном методе. - person Andrew; 10.07.2014

Вот что я придумал:

app/helpers/application_helper.rb

module ApplicationHelper

  # Public: Pick the correct arguments for form_for when shallow routes 
  # are used.
  #
  # parent - The Resource that has_* child
  # child - The Resource that belongs_to parent.
  def shallow_args(parent, child)
    params[:action] == 'new' ? [parent, child] : child
  end

end

app/views/notes/_form.html.erb

<%= simple_form_for shallow_args(@customer, @note), html: { class: 'form-vertical'} do |f| %>
  <%= f.input :content %>

  <%= f.button :submit %>
<% end -%>

Я не знаю, лучшее ли это решение, но, похоже, оно работает нормально.

person James    schedule 19.03.2012
comment
Это выглядит как достойное решение, но я действительно хотел бы, чтобы в Rails было что-то испеченное, чтобы справиться с этим. В конце концов, любой, кто использует неглубокие маршруты, обязательно должен столкнуться с этой проблемой, как только создаст свою первую форму, верно? - person imderek; 30.03.2012
comment
@imderek См. Решение Эрика ниже. Нет необходимости в специальном вспомогательном методе. - person Andrew; 10.07.2014
comment
Одним из преимуществ этого решения по сравнению с решением Эрика является меньшая связь с контроллером. С помощью этого решения вы можете назначать любые переменные экземпляра, которые вам нравятся. - person Jared Beck; 04.03.2016