Alternative for accepts_nested_attributes_for - maybe virtus

后端 未结 4 646
萌比男神i
萌比男神i 2021-02-07 04:21

I\'m relatively new to rails and finally found the right way to use accepts_nested_attributes_for.

However, there are some serious resources on the web who

相关标签:
4条回答
  • 2021-02-07 04:57

    So, the accepted answer just says why accepts_nested_attributes_for is often a good solution, but never actually offers a solution on how to do it. And the example in the linked article will run into problems if you want to make your form accept a dynamic number of nested objects. This is the only solution I've found

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

    For posterity this is the basics, but there's a bit more on the site:

    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
    

    and f.fields_for :contacts should behave like an has_many relationship, and easily handled by your form object.

    If Contact isn't an AR model you'll need to spoof persisted? as well.

    0 讨论(0)
  • 2021-02-07 05:07

    It's much easier to use virtus instead of accepts_nested_attributes_for than I thought. The most important requirement was to dare to make things that were not covered in any of the tutorials I read yet.

    Step by step:

    1. I added gem 'virtus' to the Gemfile and ran bundle install.
    2. I wrote a file models/contact.rb and wrote the following code:

      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
      
    3. Then I ran rails generate controller contacts and filled *models/contacts_controller.rb* with

      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
      
    4. The next step was the view. I created views/contacts/new.html.erb and wrote this basic form

      <%= 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 %>
      
    5. Of course I also needed to add the route resources :contacts

    That's it. Maybe it could be done more elegant. Maybe it would also pay to use only the Contacts-class, also for the other CRUD-actions. I didn't try that yet...

    You can find all changes here: https://github.com/speendo/PhoneBook/tree/virtus/app/models

    0 讨论(0)
  • 2021-02-07 05:10

    Your question implies that you believe accepts_nested_attributes functionality to be a bad thing which is totally not the case and works perfectly well.

    I'll start by saying that you don't need an alternative to accepts_nested_attributes_for but I'll cover that at the end of this post.

    With reference to the link you provide, it cites nothing about why the poster believes accepts_nested_attributes_for should be deprecated at all and just merely states

    in my humble opinion, should be deprecated

    Nested attributes are an extremely important concept when considering how to capture multiple records related to a parent in a single form which is not just a Ruby on Rails thing but used in most complex web applications for sending data back to the server from the browser regardless of of the languages used to develop the site.

    I'm not criticising the article you point to at all. To me it's just pointing out obvious alternatives to filling up a database backed model with lots of code that is not necessarily related to business logic. The specific example used is merely a coding style preference alternative.

    When time is money and the pressure is on and one line of code will do the job versus the 22 lines of code shown in the example my preference in most cases (not all cases) is to use one line of code in a model (accepts_nested_attributes_for) to accept nested attributes posted back from a form.

    To answer your question properly is impossible as you have not actually stated why YOU think accepts_nested_attributes_for is not good practice however the simplest alternative is to just extract the params hash attributes in your controller action and handle each record individually inside a transaction.

    Update - follow up on comment

    I think the author of the linked article argues that, following oop-paradigms, every object should only read and write its own data. With accepts_nested_attributes_for, one object however changes some other objects data.

    O.K. Lets clear that up. Firstly OO paradigms suggest no such thing. Classes should be discreet but they are allowed to interact with other classes. In fact there would be no point to an OO approach in Ruby if this was the case as EVERYTHING in ruby is a class therefore nothing would be able to talk to anything else. Just imagine what would happen if an object that just happens to be an instance of your controller were not able to interact with models or other controllers?

    With accepts_nested_attributes_for, one object however changes some other objects data.

    Couple of points on that statement as it's a complex one I'll try to be as brief as possible.

    1) Model instances guard the data. In very complex scenarios involving hundreds of tables in any/most other languages (C, Delphi, VB to name a few) a middle tier in a 3 tier solution does just that. In Rails terms a model is a place for business logic and does the job of the middle tier in a 3 tier solution that is normally backed up by stored procedures and views in the RDBMS. Models quite rightly should be able to talk to each other.

    2) accepts_nested_attributes_for does not break any OO principles at all. it merely simplifies the amount of code that you would need to write if the method did not exist (as you are finding out). If you accept attributes that are nested inside a params hash for child models all you are doing is allowing the child models to handle that data in the same way that your controller's action would have to do. No business logic is bypassed and you get added benefits.

    Lastly

    I can afford to care about elegance of code (more than about time)

    I can assure you that there is nothing elegant about writing 20 + more lines of code than you need to and adding hundreds of lines more code from a gem where one line of code will do the work for you. As others have stated (including me) accepts_nested_attributes_for is not always an appropriate ActiveRecord method to use and it is a good thing that you are doing by looking at different approaches as ultimately you will be able to make better informed judgements as to when to use built in methods and when to write your own. However I would suggest that to fully understand what is going on (as you state you have the time) you would be better writing your own code to handle form objects and accepts nested attributes alternatives. That way you would find yourself understanding so much more.

    Hope that makes sense and good luck with your learning.

    UPDATE 2

    To finally get to your point and in reference to your own answer plus taking into account of the excellent comments others have made on your own answer form objects backed by the virtus gem is a perfectly reasonable solution especially when dealing with the way data has to be collected. The combination helps to separate the user interface logic from the business logic and so long as you are ultimately passing off the data to the models so that business logic is not bypassed (as you show you are doing exactly this) then you have a great solution.

    Just don't rule out accepts_nested_attributes out of hand.

    You might also gain some benefit from watching the railscasts by Ryan Bates on form objects.

    0 讨论(0)
  • 2021-02-07 05:12

    Railscasts also has an episode on nested form models: http://railscasts.com/episodes/196-nested-model-form-revised?view=asciicast

    As shown in the comments, cocoon gem simplifies a lot: https://github.com/nathanvda/cocoon

    0 讨论(0)
提交回复
热议问题