Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested forms #99

Open
ujifgc opened this issue May 4, 2016 · 5 comments
Open

Nested forms #99

ujifgc opened this issue May 4, 2016 · 5 comments

Comments

@ujifgc
Copy link
Member

ujifgc commented May 4, 2016

From @fgarcia on September 9, 2014 14:37

I am not having the behavior I expected from the fields_for form helper. This is what I am trying to do (Slim template):

= form_tag '/form/posted' do

  = fields_for :model do |f|
    = f.label :email
    = f.text_field :email

The text field above is generated as:

<input type="text" name="object[email]" value="" id="object_email">

I expected the field to be inside 'model', not inside 'object':

<input type="text" name="model[email]" value="" id="object_email">

Also I cannot nest again inside like this:

= f.fields_for :inside do |ff|
  = ff.text_field :bar

With that I get the error undefined method 'inside' and my expected output was:

<input type="text" name="model[inside][bar]"  id="...">

Maybe I am doing something wrong expecting something similar to the rails helpers, so any advice using these helpers to get nested forms (without object) would be welcome.

Copied from original issue: padrino/padrino-framework#1760

@ujifgc
Copy link
Member Author

ujifgc commented May 4, 2016

From @dariocravero on September 9, 2014 16:32

@fgarcia I know it might be a bit confusing and that probably the whole form builders section needs to be redone. I'll get started using your example here :).

First of all, the form builder helpers expect the symbol you use to be either an instance variable available to the view or an object it can instantiate on the fly.

Having said that, you can still force the name of the field by using the as: :model in fields_for as in: fields_for :model, as: :model.

Here's a sample app that should work with it:

# app/app.rb
module Issue1760
  class App < Padrino::Application
    register Padrino::Helpers
    enable :sessions

    get :index do
      render :index
    end
  end
end

# app/views/index.slim
= form_for :model, '/some/specific/path' do |f|
  = f.label :email
  = f.text_field :email

#models/model.rb
class Model
end

It should render:

<form action="/form/posted" accept-charset="UTF-8" method="post">
  <label for="model_email" value="">Email: </label>
  <input type="text" name="model[email]" value="" id="model_email">
</form>

Notice how I've merged the usage of form_tag and fields_for in form_for.

Now, let's jump into nested models/data structures. The issue you're facing there is that you're trying to use an association that doesn't exist or isn't explicitly declared. The helper is only trying to access model.inside and inside is undefined for your current model object. Let's extend the app above to support associations:

# app/views/index.slim
= form_for :model, '/some/specific/path' do |f|
  = f.label :email
  = f.text_field :email

  = f.fields_for :inside do |fi|
    = fi.label :name
    = fi.text_field :name

# models/model.rb
class Model
  def inside
    Inside.new
  end
end

# models/inside.rb
class Inside
end

That would render something like:

<form action="/form/posted" accept-charset="UTF-8" method="post">
  <label for="model_email" value="">Email: </label>
  <input type="text" name="model[email]" value="" id="model_email">
  <label for="model_inside_attributes_name" value="">Name: </label>
  <input type="text" name="model[inside_attributes][name]" value="" id="model_inside_attributes_name">
</form>

That would be the case of a one to one association in a relational db. If you were to return an array of elements, it would then render as many as you return. So, say we modify `models/model.rb to look like this:

# models/model.rb
class Model
  def inside
    [Inside.new, Inside.new]
  end
end

It should output:

<form action="/form/posted" accept-charset="UTF-8" method="post">
  <label for="model_email" value="">Email: </label>
  <input type="text" name="model[email]" value="" id="model_email">
  <label for="model_inside_attributes_0_name" value="">Name: </label>
  <input type="text" name="model[inside_attributes][0][name]" value="" id="model_inside_attributes_0_name">
  <label for="model_inside_attributes_1_name" value="">Name: </label>
  <input type="text" name="model[inside_attributes][1][name]" value="" id="model_inside_attributes_1_name">
</form>

If we were to add an ORM like, say Sequel, and name the models Parent and Child with a one parent to many children relationship, we would end up with something along the lines of:

# app/views/index.slim
= form_for :parent, url(:parents, :index) do |f|
  = f.label :email
  = f.text_field :email

  = f.fields_for :children do |fi|
    = fi.label :name
    = fi.text_field :name

  = f.submit

# app/controllers.rb
Issue1760::App.controllers :parents do
  get :new do
    @parent = Parent.new
    @parent.children << Child.new
    render :index
  end

  get :view, with: :id do
    @parent = Parent[params[:id]]
    render :index
  end

  post :index do
    parent = Parent.create(email: params['parent']['email'])

    params['parent']['children_attributes'].each do |i, child_data|
      Child.create(parent_id: parent.id, name: child_data['name'])
    end

    redirect url(:parents, :view, parent.id)
  end
end

# models/parent.rb
class Parent < Sequel::Model
  one_to_many :children
end

# models/child.rb
class Child < Sequel::Model
  many_to_one :parent
end

And that's kind of how the whole relations thing would go when you layered up to using ORM models together with the form builder.

Hope this helped clarify things a bit and if not, let me know and we'll look at it in more depth. Here's the demo app.

:)

@ujifgc
Copy link
Member Author

ujifgc commented May 4, 2016

From @dariocravero on September 9, 2014 16:32

@padrino/core-members should we update the guides with something along the lines of this?

@ujifgc
Copy link
Member Author

ujifgc commented May 4, 2016

From @fgarcia on September 9, 2014 17:0

That was more exhaustive than I expected! Thanks a lot

I downloaded your example, played with it and verified one thing... when you mentioned instantiate on the fly we made different assumptions. Somewhere in the doc I also read that and thought about instantiate without a model

Your example uses form_for which I thought could achieve the result I wanted using a real model. Now it is so much clear!

However in my example I used form_tag because I wanted to use the helpers without an existing model. My assumption was that the helpers behaved like in Rails. I've just tested this quickly in Rails:

<%= form_tag do %>
    Form contents
    <%= text_field_tag(:outside) %>
    <%= fields_for :model do |f| %>
      <%= f.text_field(:inside) %>
      <%= f.fields_for :again do |ff| %>
        <%= ff.text_field(:deeper) %>
      <% end %>
    <% end %>
<% end %>

The last line is rendered as:

<input id="model_again_deeper" name="model[again][deeper]" type="text">

That was my expectation when combining fields_for with form_tag

It is my fault misunderstanding since I assumed that if fields_for could be used within form_tag an no prefixed model, then I expected that no model at all was required :-(

thanks for the lesson!

@ujifgc
Copy link
Member Author

ujifgc commented May 4, 2016

From @dariocravero on September 9, 2014 17:31

I get your pain point and I agree that it should probably work without an explicit model too, e.g., what if you need to create a form for a service you don't manage in the same app but you know the structure of? Perhaps we could try to mimic that behaviour @padrino/core-members thoughts?

@ujifgc
Copy link
Member Author

ujifgc commented May 4, 2016

From @halfdan on November 6, 2015 11:54

It'd also be great if it would allow to render an "Add" button that adds additional nested elements on the fly (in the DOM, using javascript).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant