Подтвердить что ты не робот

Rails - как заполнить идентификатор родительского объекта, используя вложенные атрибуты для дочернего объекта и сильные параметры?

У меня есть ситуация, подобная представленной в Railscast 196-197: Вложенная форма модели. Однако я столкнулся с конфликтом между этим подходом и сильными параметрами. Я не могу найти хороший способ заполнить поле id родительской записи на дочернем объекте, так как я не хочу, чтобы это было назначено через форму (чтобы пользователи не связывали дочерние записи с родительскими записями, которые они не имеют). У меня есть решение (см. Код ниже), но это похоже на то, что Rails может иметь умный и простой способ сделать для меня.

Здесь код...

Здесь есть родительский объект (вызовите его Survey), у которого есть дочерние объекты has_many (назовите их "Вопросы" ):

# app/models/survey.rb
class Survey
    belongs_to :user
    has_many :questions
    accepts_nested_attributes_for :questions
end

# app/models/question.rb
class Question
    validates :survey_id, :presence => true
    belongs_to :survey
end

Существует форма, позволяющая пользователям одновременно создавать опрос и вопросы об этом опросе (для простоты приведенный ниже код рассматривает опросы, как будто они имеют только вопрос):

# app/views/surveys/edit.html.erb
<%= form_for @survey do |f| %>
    <%= f.label :name %>
    <%= f.text_field :name %><br />
    <%= f.fields_for :questions do |builder| %>
        <%= builder.label :content, "Question" %>
        <%= builder.text_area :content, :rows => 3 %><br />
    <% end %>
    <%= f.submit "Submit" %>
<% end %>

Проблема заключается в контроллере. Я хочу защитить поле survey_id в записи вопроса с помощью сильных параметров, но при этом вопросы не проходят проверку, так как survey_id является обязательным.

# app/controllers/surveys_controller.rb
class SurveysController
    def edit
        @survey = Survey.new
        Survey.questions.build
    end

    def create
        @survey = current_user.surveys.build(survey_params)
        if @survey.save
            redirect_to @survey
        else
            render :new
        end
    end

    private

    def survey_params
        params.require(:survey).permit(:name, :questions_attributes => [:content])
    end
end

Единственный способ решить эту проблему - собрать вопросы отдельно от опроса следующим образом:

def create
    @survey = current_user.surveys.build(survey_params)
    if @survey.save
        if params[:survey][:questions_attributes]
            params[:survey][:questions_attributes].each_value do |q|
                question_params = ActionController::Parameters.new(q)
                @survey.questions.build(question_params.permit(:content))
            end
        end
        redirect_to @survey
    else
        render :new
    end
end

private

def survey_params
    params.require(:survey).permit(:name)
end

(Rails 4 beta 1, Ruby 2)

UPDATE

Возможно, лучший способ справиться с этой проблемой состоит в том, чтобы разделить "объект формы", как предлагается в этом сообщении в блоге Code Climate. Я оставляю вопрос открытым, хотя, поскольку мне любопытно в других точках зрения

4b9b3361

Ответ 1

Итак, проблема, с которой вы сталкиваетесь, заключается в том, что дочерние объекты не проходят проверку, правильно? Когда дочерние объекты создаются одновременно с родителем, дочерние объекты не могут знать идентификатор своего родителя, чтобы пройти проверку, это правда.

Вот как вы можете решить эту проблему. Измените свои модели следующим образом:

# app/models/survey.rb
class Survey
    belongs_to :user
    has_many :questions, :inverse_of => :survey
    accepts_nested_attributes_for :questions
end

# app/models/question.rb
class Question
    validates :survey, :presence => true
    belongs_to :survey
end

Различия здесь в :inverse_of, переданных ассоциации has_many, и что вопрос теперь проверяется только на :survey вместо :survey_id.

:inverse_of делает так, что когда дочерний объект создается или создается с использованием ассоциации, он также получает обратную ссылку на родителя, который его создал. Это похоже на то, что должно произойти автоматически, но, к сожалению, это не произойдет, если вы не укажете эту опцию.

Проверка :survey вместо :survey_id - это скорее компромисс. Валидация уже не просто проверяет наличие чего-то непустого в поле survey_id; теперь он фактически проверяет связь для существования родительского объекта. В этом случае это полезно из-за :inverse_of, но в других случаях ему действительно нужно будет загрузить ассоциацию из базы данных, используя идентификатор, чтобы проверить. Это также означает, что идентификаторы, не соответствующие чему-либо в базе данных, не пройдут проверку.

Надеюсь, что это поможет.