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 say that using accepts_nested_attributes_for
is generally a bad practice (like this one).
What changes are necessary to avoid accepts_nested_attributes_for
and in which folder would you put the additional class-file (I guess one needs an additional class).
I read that virtus is appropriate for that. Is that right?
Here is a very basic example still using accepts_nested_attributes_for
(find the full example here):
Models
class Person < ActiveRecord::Base
has_many :phones
accepts_nested_attributes_for :phones
end
class Phone < ActiveRecord::Base
belongs_to :person
end
Controller
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
View (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 %>
[edit]
Would it be a good idea to use a service object?
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.