I'm using the Cocoon gem to build a nested form with a field_for containing another field_for. The hierarchy looks like this: -Letter form -Card field_for -Button field_for
I have a link_to_add_association
in order to dynamically add cards with their buttons. This part looks like this:
<div class="connected-carousels">
<div class="stage">
<div class="carousel carousel-stage">
<ul id="carousel-stage-ul">
<%= f.fields_for :cards do |card_fields| %>
<% if @blabla %>
<%= render 'card_fields', f: card_fields %>
<% end %>
<% end %>
<% end %>
</ul>
</div>
<a href="#" class="prev prev-stage"><span>‹</span></a>
<a href="#" class="next next-stage"><span>›</span></a>
</div>
<div class="navigation">
<a href="#" class="prev prev-navigation">‹</a>
<a href="#" class="next next-navigation">›</a>
<div class="carousel carousel-navigation">
<ul id="carousel-navigation-ul"></ul>
</div>
</div>
<div>
<%= link_to_add_association '+ Add Card', f, :cards, id: 'add-card-button-bis', data: { association_insertion_node: '#carousel-stage-ul', association_insertion_method: :append } %>
</div>
</div>
_card_fields.html.erb partial rendering the buttons:
<% f.object.buttons.build %>
<%= f.fields_for :buttons do |button_card_fields| %>
<%= render 'button_fields', f: button_card_fields %>
<% end %>
_button_fields.html.erb partial:
<div class="add-button-card-modal">
<h4>Add New Button</h4>
<label>Button Text</label>
<%= f.text_field :button_text, :maxlength => 20, placeholder: "Enter the text to display on the button..." %>
<br><br>
<label>Button URL</label>
<%= f.text_field :button_url, placeholder: "Paste URL..." %>
<div class="nav-popups-buttons">
<button type="button" id="validate_new_card_button" class="small-cta2">Add Button</button>
<p class="remove-link" id="delete_new_card_button">Remove Button</p>
</div>
</div>
Letter model:
class Letter < ApplicationRecord
validates :campaign_name, :presence => true
belongs_to :core_bot
has_many :messages, dependent: :destroy
has_many :cards, dependent: :destroy
has_many :filters, dependent: :destroy
has_many :analytic_deliveries, dependent: :destroy
has_many :analytic_reads, dependent: :destroy
has_many :analytic_sends, dependent: :destroy
accepts_nested_attributes_for :filters, allow_destroy: true, :reject_if => :all_blank
accepts_nested_attributes_for :messages, allow_destroy: true, :reject_if => :all_blank
accepts_nested_attributes_for :cards, allow_destroy: true, :reject_if => :all_blank
end
Card model:
class Card < ApplicationRecord
validates :remote_image_url, :format => URI::regexp(%w(http https)), presence: { message: '%{value} : please enter valid url' }, :allow_blank => true
validates :title, :subtitle, :presence => true
belongs_to :letter, optional: true
has_many :buttons, dependent: :destroy
accepts_nested_attributes_for :buttons, :reject_if => Proc.new { |att| att[:button_text].blank? && att[:button_url].blank? }, allow_destroy: true
end
Button model:
class Button < ApplicationRecord
validates :button_url, :format => URI::regexp(%w(http https)), presence: { message: '%{value} : please enter valid url' },
unless: Proc.new { |a| a.button_url.blank? }
validates :button_text, :presence => true, unless: Proc.new { |a| a.button_url.blank? }
belongs_to :message, optional: true
belongs_to :card, optional: true
has_one :short_url, dependent: :destroy
end
Create action in the letter controller in case of error raised:
@letter.filters.build
if params[:letter]['cards_attributes'].present? == false
@letter.cards.build.buttons.build
end
format.html { render :new }
format.json { render json: @letter.errors, status: :unprocessable_entity }
The problem is that buttons are not built when the create action raises an error or when I edit the record. If I add @letter.cards.build.buttons.build
into the controller, it adds a new card but buttons inputs still don't appear.
UPDATE In fact buttons are built. The only problem is my custom jquery I am using to display the popup showing the buttons fields are not working since there were created in the cocoon:after-insert callback.
When an error is raised or on the edit view, the cards I created are rendered instead of added by link_to_add_association. This the cocoon:after-insert callback is not called...
Any idea how I could use the same logic of link_to_add_association calling the after-insert callback on rendered cards fields?
Ok, so normally there's a couple issues buried in one of these problems. This answer is only to trouble shoot why the Edit
view always fails to load.
My initial guess is in the controller, two problems ... the if params
& the @letter.cards.build.buttons.build
is the problem & instead of trying to design around it - you need to change it, accept whatever the error is & then fix that error in the models. The first fix is get the conditional if params
out of there.
I also noticed there's no accept_nested_attributes_for :buttons
on your letter model.
Lastly, before we begin trouble shooting - I forgot to ask for your strong_params section - please post the entire controller action for create
& the strong_params method in the private section of the controller.
Since it's not loading at all. Ever. It's probably not a totally a jquery issue. We should see a rails generated collection of all the cards & buttons belonging via the model.
I'm guessing so the .build
method is the first issue ...
I imagine if you examine the rails console ... rails c
...
First, try calling @test = Letter.first
, then explore it's values when it sets ... you should be able to see other ID's in there & then be able to call them with @test.{whatever}
.. ie: @test.cards
which will give a collection of all the cards that exist via the relationship in the models.
Second, if the first fails, @test.cards.create!(name: "test")
to see if the relationship creates the entry correctly. I'd be happy if anything shows up in @letter.cards.buttons
in console, then we trouble shoot the other branches (letter.filters
) of your hierarchy later.
Third, we move generating test data via console to ensure those values exist to be fed thru the rails controller & to the view to be displayed.
Fourth, we check the jquery isn't interfering with them being displayed.
Demo code
Here's the 3 deep nesting ...
Note all of this worked & took no effort for the EDIT
, the setup for new/create in the _form.html.erb
just automatically worked for editing.
Models
country.rb
class Country < ApplicationRecord
has_many :states
has_many :counties, through: :states
accepts_nested_attributes_for :states, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
accepts_nested_attributes_for :counties, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
end
state.rb
class State < ApplicationRecord
belongs_to :country
has_many :counties
validates :name, presence: true
# can't recall if this is needed
accepts_nested_attributes_for :counties, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
end
county.rb
class County < ApplicationRecord
belongs_to :state
validates :name
end
countries_controller.rb ... Controller (you don't even need to generate a controller for others)
class CountriesController < ApplicationController
before_action :set_country, only: [:show, :edit, :update, :destroy]
def index
@countries = Country.paginate(page: params[:page], per_page: 10)
end
def show
end
def new
@country = Country.new
end
def edit
# @partial_choice = params[:partial_choice]
end
def create
@country = Country.new(country_params)
respond_to do |format|
if @country.save
format.html { redirect_to @country, notice: 'Country was successfully created.' }
format.json { render :show, status: :created, location: @country }
else
format.html { render :new }
format.json { render json: @country.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @country.update!(country_params)
format.html { redirect_to @country, notice: 'Country was successfully updated.' }
format.json { render :show, status: :ok, location: @country }
else
format.html { render :edit }
format.json { render json: @country.errors, status: :unprocessable_entity }
end
end
end
def destroy
@country.destroy
respond_to do |format|
format.html { redirect_to countries_url, notice: 'Country was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_country
@country = Country.find(params[:id])
end
def country_params
params.require(:country).permit(:id, :name, :description, :size, :player_id,
countryneighbor_attritubtes: [:id, :bordercountry_id, :country_id, :_destroy],
states_attributes: [:id, :name, :description, :country_id, :_destroy,
counties_attributes: [:id, :name, :description, :state_id, :_destroy]])
end
end
Views - if you've already tested the data & relationships, strong_params
in rails console as well as checked server logs in log/development.log
, then maybe compare the HAML style calls in the views here ...
/basicB/app/views/countries/_form.html.haml
.fieldset.form-inline
= simple_form_for @country do |f|
= f.error_notification
.countries
= f.input :name, input_html: {:placeholder => "...insert country name..."}
= f.input :description
.row-fuild
%a.btn.btn-primary{"aria-controls" => "neighborsDisplay", "aria-expanded" => "false", "data-toggle" => "collapse", :href => "#neighborsDisplay"} Show Neighbors
#neighborsDisplay.collapse
.list-group
- for neighbor in @country.neighbors
= render partial: 'country_neighbors', locals: {neighbor: neighbor}
.states
= f.simple_fields_for :states do |state|
= render 'state_fields', :f => state
.links.row
= link_to_add_association 'Add State', f, :states, render_options: { wrapper: 'inline_form' }, :class => "btn btn-default"
.form-actions.row
= f.button :submit
/app/views/countries/_state_fields.html.haml
.nested-fields.list-group-item
.well
%h4 State
.form-inline.text
= f.input :name, input_html: {:placeholder => "...insert State Name ..."}
= f.input :description
= link_to_remove_association f, class: 'btn btn-default btn-xs' do
.glyphicon.glyphicon-remove
.counties
= f.simple_fields_for :counties do |state|
= render 'county_fields', :f => state
.links.row
= link_to_add_association 'Add County', f, :counties, render_options: { wrapper: 'inline_form' }, :class => "btn btn-default"
/app/views/countries/_county_fields.html.haml
.nested-fields.list-group-item.my-well
.row.text
= f.input :name, input_html: {:placeholder => "... County Name ..."}
= f.input :description
= link_to_remove_association f, class: 'btn btn-default btn-xs' do
.glyphicon.glyphicon-remove