Альтернатива для accepts_nested_attributes_for - возможно, virtus - программирование
Подтвердить что ты не робот

Альтернатива для accepts_nested_attributes_for - возможно, virtus

Я относительно новичок в рельсах и, наконец, нашел правильный способ использования accepts_nested_attributes_for.

Однако в Интернете есть серьезные ресурсы, которые говорят, что использование accepts_nested_attributes_for обычно плохое (например, один).

Какие изменения необходимы, чтобы избежать accepts_nested_attributes_for и в какую папку вы поместили бы дополнительный файл класса (я думаю, нужен дополнительный класс).

Я читал, что virtus подходит для этого. Правильно ли это?

Вот очень простой пример, по-прежнему использующий accepts_nested_attributes_for (найти полный пример здесь):

Модели

class Person < ActiveRecord::Base

    has_many :phones
    accepts_nested_attributes_for :phones

end

class Phone < ActiveRecord::Base

    belongs_to :person

end

контроллер

class PeopleController < ApplicationController

    def new

        @person = Person.new
        @person.phones.new

    end

    def create

        @person = Person.new(person_params)
        @person.save

        redirect_to people_path

    end

    def index

        @people = Person.all

    end

private

    def person_params

        params.require(:person).permit(:name, phones_attributes: [ :id, :number ])

    end

end

Просмотр (people/new.html.erb)

<%= form_for @person, do |f| %>
    <p>
        <%= f.label :name %><br />
        <%= f.text_field :name %>
    </p>
    <%= f.fields_for :phones do |builder| %>
    <p>
            <%= builder.label :number %><br />
            <%= builder.text_field :number %>
    </p>
    <% end %>
    <%= f.submit %>
<% end %>

[править]
Было бы неплохо использовать служебный объект?

4b9b3361

Ответ 1

В вашем вопросе подразумевается, что вы считаете, что функция accepts_nested_attributes является плохой, что совершенно не так и работает отлично.

Начну с того, что вам не нужна альтернатива accepts_nested_attributes_for, но я расскажу об этом в конце этого сообщения.

Что касается ссылки, которую вы предоставляете, в ней ничего не говорится о том, почему плакат полагает, что accepts_nested_attributes_for должен быть устаревшим вообще и просто состоять из состояний

по моему скромному мнению, должно быть устаревшим

Вложенные атрибуты являются чрезвычайно важной концепцией при рассмотрении того, как захватывать несколько записей, связанных с родителем, в одной форме, которая является не только вещью Ruby on Rails, но используется в большинстве сложных веб-приложений для отправки данных обратно на сервер из независимо от языков, используемых для разработки сайта.

Я не критикую статью, на которую вы указываете. Для меня это просто указывает на очевидные альтернативы заполнению модели с поддержкой базы данных с большим количеством кода, который не обязательно связан с бизнес-логикой. Используемый конкретный пример - это просто альтернатива предпочтения стиля кодирования.

Когда время - это деньги, и давление включено, и одна строка кода выполняет задание по сравнению с 22 строками кода, показанным в примере, мое предпочтение в большинстве случаев (не во всех случаях) заключается в использовании одной строки кода в model (accepts_nested_attributes_for), чтобы принять вложенные атрибуты, отправленные обратно из формы.

Правильно ответить на ваш вопрос невозможно, поскольку вы на самом деле не заявили, почему вы думаете, что accepts_nested_attributes_for не является хорошей практикой, но самой простой альтернативой является просто извлечь атрибуты хэша params в действие вашего контроллера и обрабатывать каждую запись отдельно внутри транзакции.

Обновление - отслеживание комментариев

Я думаю, что автор связанной статьи утверждает, что, следуя oop-парадигмы, каждый объект должен только читать и писать свои собственные данные. С accepts_nested_attributes_for один объект, однако, меняет некоторые другие объекты.

O.K. Давайте это выясним. Во-первых, парадигмы ОО не предполагают такой вещи. Классы должны быть осторожными, но им разрешено взаимодействовать с другими классами. На самом деле в Ruby не было бы никакого смысла использовать OO-подход, если бы это было так, потому что все в рубине было классом, поэтому ничто не могло бы говорить ни с чем другим. Представьте себе, что произойдет, если объект, который просто является экземпляром вашего контроллера, не смог взаимодействовать с моделями или другими контроллерами?

С accepts_nested_attributes_for, однако один объект изменяет некоторые другие объекты.

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

1) экземпляры экземпляров защищают данные. В очень сложных сценариях, включающих сотни таблиц на любом/большинстве других языков (C, Delphi, VB, чтобы назвать несколько), средний уровень в трехуровневом решении делает именно это. В терминах Rails модель представляет собой место для бизнес-логики и выполняет работу среднего уровня в трехуровневом решении, которое обычно резервируется хранимыми процедурами и представлениями в РСУБД. Модели вполне справедливо должны иметь возможность разговаривать друг с другом.

2) accepts_nested_attributes_for не нарушает никаких принципов OO вообще. это просто упрощает объем кода, который вам нужно будет писать, если метод не существует (как вы узнаете). Если вы принимаете атрибуты, вложенные внутри хэша params для дочерних моделей, все, что вы делаете, позволяет дочерним моделям обрабатывать эти данные так же, как это необходимо для действия вашего контроллера. Никакая бизнес-логика не обходится, и вы получаете дополнительные преимущества.

Наконец

Я могу позволить себе заботиться об элегантности кода (больше, чем о времени)

Я могу заверить вас, что нет ничего элегантного в написании 20 + больше строк кода, чем вам нужно, и добавлении сотен строк большего количества кода из драгоценного камня, где одна строка кода будет работать для вас. Как утверждали другие (включая меня) accepts_nested_attributes_for, не всегда используется подходящий метод ActiveRecord, и это хорошо, что вы делаете, рассматривая разные подходы, так как в конечном итоге вы сможете лучше понять, когда использовать встроенные в методах и когда писать свои собственные. Однако я бы предположил, что для того, чтобы полностью понять, что происходит (как вы заявляете, у вас есть время), вам лучше писать свой собственный код для обработки объектов формы и принимать альтернативы вложенных атрибутов. Таким образом, вы бы поняли гораздо больше.

Надеюсь, что это имеет смысл и удачи в вашем обучении.

ОБНОВЛЕНИЕ 2

Чтобы, наконец, дойти до вашей точки и в отношении вашего собственного ответа, а также с учетом замечательных комментариев, сделанных другими пользователями на ваших собственных объектах формы ответа, поддерживаемых virtus gem, является вполне разумным решением, особенно при работе с данными должен быть собран. Комбинация помогает отделить логику пользовательского интерфейса от бизнес-логики и до тех пор, пока вы в конечном счете передаете данные моделям, чтобы бизнес-логика не была обойдена (как вы показываете, вы делаете именно это), тогда у вас есть отличное решение.

Просто не исключайте, что accepts_nested_attributes вышел из строя.

Вы также можете получить некоторую выгоду от просмотра railscasts Райана Бейтса на объектах формы.

Ответ 2

Гораздо проще использовать virtus вместо accepts_nested_attributes_for, чем я думал. Наиболее важным требованием было осмелиться сделать вещи, которые не были охвачены ни в одном из учебных пособий, которые я прочитал еще.

Шаг за шагом:

  • Я добавил gem 'virtus' в Gemfile и запустил bundle install.
  • Я написал файловые модели /contact.rb и написал следующий код:

    class Contact
      include Virtus
    
      extend ActiveModel::Naming
      include ActiveModel::Conversion
      include ActiveModel::Validations
    
      attr_reader :name
      attr_reader :number
    
    
      attribute :name, String
      attribute :number, Integer
    
      def persisted?
        false
      end
    
      def save
        if valid?
          persist!
          true
        else
          false
        end
      end
    
    private
    
      def persist!
        @person = Person.create!(name: name)
        @phones = @person.phones.create!(number: number)
      end
    end
    
  • Затем я запустил rails generate controller contacts и заполнил * models/contacts_controller.rb * с помощью

    class ContactsController < ApplicationController
    
      def new
    
        @contact = Contact.new
    
      end
    
      def create
    
        @contact = Contact.new(contact_params)
        @contact.save
        redirect_to people_path
    
      end
    
      def contact_params
    
        params.require(:contact).permit(:name, :number)
    
      end
    
    end
    
  • Следующим шагом было представление. Я создал views/contacts/new.html.erb и написал эту базовую форму

    <%= form_for @contact do |f| %>
      <p>
        <%= f.label :name %><br />
        <%= f.text_field :name %>
      </p>
    
      <p>
        <%= f.label :number %><br />
        <%= f.text_field :number %>
      </p>
    
      <%= f.submit %>
    <% end %>
    
  • Конечно, мне также нужно было добавить маршрут resources :contacts

Что это. Возможно, это можно сделать более элегантным. Возможно, он также заплатит за использование только класса "Контакты", а также за другие CRUD-действия. Я еще не пробовал...

Здесь вы можете найти все изменения: https://github.com/speendo/PhoneBook/tree/virtus/app/models

Ответ 3

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

https://coderwall.com/p/kvsbfa/nested-forms-with-activemodel-model-objects

Для потомков это основы, но там немного больше на сайте:

class ContactListForm
include ActiveModel::Model

attr_accessor :contacts

def contacts_attributes=(attributes)
  @contacts ||= []
  attributes.each do |i, contact_params|
    @contacts.push(Contact.new(contact_params))
  end
end
end

class ContactsController < ApplicationController
   def new
      @contact_list = ContactListForm.new(contacts: [Contact.new])
    end
  end

и f.fields_for :contacts должны вести себя как отношения has_many и легко обрабатываться вашим объектом формы.

Если Contact не является моделью AR, вам также нужно обмануть persisted?.