How to create a double nested form in Phoenix Framework? - forms

My issue is that I keep on getting an argument error when trying to add subsections to my interview form which is nested under sections.
The Interview_kit has many sections and sections have many sub_sections. The sections belong to the interview_kit and the subsections belong to the section.
I would like to create all of this in the same form but am haveing no luck.
Is it possible to do this in Phoenix?
# Error
ArgumentError at GET /interview_Kit/new could not generate inputs for
:sub_sections from App.InterviewKit. Check the field
exists and it is one of embeds_one, embeds_many, has_one, has_many,
belongs_to or many_to_many
# This error is being shown when trying to render as a string in the method
link_to_sub_sections_field() below
# fields = render_to_string(__MODULE__, "sub_section.html", f: form)
# Schemas
schema "interview_kits" do
field :name, :string
field :description, :string
has_many :sections, AppSection
timestamps()
end
schema "sections" do
field :title, :string
timestamps()
belongs_to :interview_kit, App.InterviewKit
has_many :sub_sections, App.SubSection
end
schema "sub_sections" do
field :field_name, :string
belongs_to :section, App.Section
end
defmodule App.InterviewKitView do
def link_to_sub_section_fields() do
changeset = InterviewKit.changeset(%InterviewKit{sections: [%Section{sub_sections: [%SubSection{}]}]})
form = Phoenix.HTML.FormData.to_form(changeset, [])
fields = render_to_string(__MODULE__, "sub_section.html", f: form)
link "Add Sub Section", to: "#", "data-template": fields, id: "add_sub_section"
end
end
# sub_sections.html.eex
<%= inputs_for #f, :sub_sections, [multipart: true], fn fo -> %>
<div class="row">
<div class="col-md-11">
<%= input fo, :field_name %>
</div>
<div class="col-md-1">
<i class="fa fa-trash" aria-hidden="true"></i></h5>
</div>
</div>
<% end %>
# what I am trying to accomplish
interview_params = %{"_csrf_token" => "PDwZA==",
"_utf8" => "✓", "files" => "",
"interview_kit" => %{"description" => "<p>dsadasdasmoimoimadsads</p>",
"name" => "First Interview", "sections" => %{"0" => %{"name" => "sadasdsad"},
"sub_sections" => %{"0" => %{"field_name" => "sadasdsad"}}}}}

Issue was I was not reiterating over the sections form in my sub_section.html.eex
# sub_section.html.eex
# Correct way
<%= inputs_for #f, :sections, [multipart: true], fn fo -> %>
<div class="row">
<div class="col-md-11">
<%= inputs_for fo, :sub_sections, [multipart: true], fn po ->%>
<%= input po, :field_name %>
<% end %>
</div>
<div class="col-md-1">
<i class="fa fa-trash" aria-hidden="true"></i></h5>
</div>
</div>
<% end %>
# sub_section.html.eex
# Wrong way
<%= inputs_for #f, :sub_sections, [multipart: true], fn fo -> %>
<div class="row">
<div class="col-md-11">
<%= input fo, :field_name %>
</div>
<div class="col-md-1">
<i class="fa fa-trash" aria-hidden="true"></i></h5>
</div>
</div>
<% end %>
# section.html.eex
<%= inputs_for #f, :sections, [multipart: true], fn po -> %>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">
<%= input po, :name %>
</div>
</div>
<div class="panel-body">
<% inputs_for po, :sub_sections, [multipart: true], fn fo -> %>
<div class="row">
<div class="col-md-11">
<%= input fo, :field_name %>
</div>
</div>
<% end %>
<h5><%= link_to_sub_section_fields() %></h5>
</div>
</div>
<!-- END PANEL -->
</div>
<%= link "Remove Section", to: "#", id: "delete_section" %>
</div>
<% end %>
#interview_kit_view.ex
def link_to_sub_section_fields() do
changeset = InterviewKit.changeset(%InterviewKit{sections: [%Section{sub_sections: [%SubSection{}]}]})
form = Phoenix.HTML.FormData.to_form(changeset, [])
fields = render_to_string(__MODULE__, "sub_section.html", f: form)
link "Add Sub Section", to: "#", "data-template": fields, id: "add_sub_section"
end
interview_kit.js
handleAdd() {
$(document).on("click", "#add_section, #add_sub_section", function(e) {
console.log($(e));
e.preventDefault();
let time = new Date().getTime();
let template = $(this).data("template");
let uniq_template = template.replace(/\[0\]/g, `[${time}]`);
uniq_template = uniq_template.replace(/_0_/g, `_${time}_`);
$(this).after(uniq_template);
});
}
I am not sure I fully understand why I have to reiterate over the sections form in my partial but it gives me the correct params on submit I was looking for.
%{"_csrf_token" => "AlgJcnNQw==",
"_utf8" => "✓", "files" => "",
"interview_kit" => %{"description" => "<p>dsadsadas</p>",
"name" => "name",
"sections" => %{"1495809951376" => %{"name" => "sadsadas",
"sub_sections" => %{"1495809951376" => %{"name" => "field_param"}}}}}}

Related

Can you the coccon gem without a direct nested association?

I have to convert my form to not using nested association. In other words, instead of
<%= link_to_add_association f, :contacts, class: 'btn btn-primary', partial: 'projects/contact_fields', data: {
association_insertion_node: '.contact_fields', association_insertion_method: :append
} do %>
<i class="fas fa-plus"></i>
<% end %>
<%= f.fields_for :contacts do |contact| %>
<%= render 'projects/contact_fields', f: contact %>
<% end %>
I would like to be able to just pass in a string to be used as the container (similar to how you can with field_for).
<%= link_to_add_association 'contacts[]', 'projects/contact_fields', class: 'btn btn-primary', partial: 'projects/contact_fields', data: {
association_insertion_node: '.contact_fields', association_insertion_method: :append
} do %>
<i class="fas fa-plus"></i>
<% end %>
<% #contacts.each_with_index do |contact, index| %>
<%= fields_for "contacts[#{index}]", contact do |c| %>
<%= render 'projects/contact_fields', f: c %>
<% end %>
<% end %>
Cocoon is currently unable to edit/manage a collection. Cocoon is just helping the form-behaviour for nested-children functionality in rails, so there is no simple solution to edit an array or collection. On the other hand, this is in general pretty simple to implement without cocoon.
Very high level, do something like:
#contacts.each do |contact|
= render `contacts/edit`, contact: contact
= render `contacts/new`
so render the edit form for each existing contact, and render an empty new form. You will have to edit your controller functionality a little, because you will always re-render the complete collection/index page (with all existing contacts in the collection?).
So you can just render multiple forms on one page. Using turbolinks this will render fast and feel actually completely the same. You could use xhr to update only specific parts of the page, but to get started that is not even needed.
So I ended up following drifting ruby and using code from the cocoon gem to implement something myself. I hope others can benefit from it. Thank you nathanvda for the cocoon gem which helped me with the code below, really wish I could have used it:
add this to your app/helpers/application_helper.rb
def link_to_add_row(*args, &block)
if block_given?
link_to_add_row(capture(&block), *args)
else
#byebug
name, association, new_object, partial, html_options = *args
html_options ||= {}
html_options[:class] = [html_options[:class], "custom_add_fields"].compact.join(' ')
id = 'NEW_RECORD'
fields = fields_for("#{association}[#{id}]", new_object, child_index: id) do |builder|
#byebug
render( partial, f: builder)
end
fields = CGI.escapeHTML(fields).html_safe
link_to(name, '#', class: html_options[:class], data: {id: id, fields: fields})
end
end
add to your app/assets/application.js
$(document).on('click', '.custom_remove_fields', function(event) {
$(this).prev('input[type=hidden]').val('1');
$(this).closest('tr').hide();
return event.preventDefault();
});
$(document).on('click', '.custom_add_fields', function(event) {
var regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data('id'), 'g');
$('.contact_fields').append($(this).data('fields').replace(regexp, time));
return event.preventDefault();
});
In your template you can use the following to render a partial for the collection:
<%= link_to_add_row('contacts', contact.new, 'contact_fields', class: 'btn btn-primary') do %>
<i class="fas fa-plus"></i>
<% end %>
This is how I render the partial with the collections in my template:
<tbody class="contact_fields">
<% #contacts.each_with_index do |contact, index| %>
<%= fields_for "contacts[#{index}]", contact do |c| %>
<%= render 'contact_fields', f: c %>
<% end %>
<% end %>
</tbody>
This is what my contact_fields.html.erb partial looks like.
<tr class="nested-fields">
<td>
<%= f.text_field :fullname, class: 'form-control invoke-contacts-search contact-fullname' %>
</td>
<td>
<%= f.text_field :email, class: 'form-control invoke-contacts-search contact-email' %>
</td>
<td>
<%= f.text_field :phone, class: 'form-control contact-phone' %>
</td>
<td>
<%= f.text_field :department, class: 'form-control contact-department' %>
</td>
<td>
<%= f.text_field :manager, class: 'form-control contact-manager' %>
</td>
<td>
<%= f.hidden_field :id %>
<%= f.hidden_field :_destroy %>
<%= link_to '#', class: 'btn btn-danger custom_remove_fields' do %>
<i class="fas fa-trash-alt"></i>
<% end %>
</td>
</tr>

Rails 4 ActionMailer Create and send email from form

I am new to actionmailer and can't seem to get ActionMailer to work and route properly, not sure if I'm doing it properly and most tutorials only show how to generate a generic email after a user has first created an account.
What I am trying to do is allow a user to generate an email through a form to send an email message with a link to a view to their clients to view an invoice.
My current error is due to some routing issues but the strange thing was that it's looking for an email template in my controller instead of the mailer controller is my assumption... Missing template invoices/send_invoice_email, application/send_invoice_email
Here is my form which the user would use to build the email.
<%= bootstrap_form_tag url: '/send_invoice' do |l| %>
<%= l.hidden_field :invoice, value: #quote.id %>
<div class="input-group margin-bottom-20">
<span class="input-group-addon" id="from"><i class="fa fa-envelope"> </i></span>
<%= l.text_field :from, hide_label: true, value: current_user.email %>
</div>
<div class="input-group margin-bottom-20">
<span class="input-group-addon" id="to"><i class="fa fa-user"> </i></span>
<%= l.text_field :to, hide_label: true, value: #quote.client.contacts.first.email %>
</div>
<div class="input-group margin-bottom-20">
<span class="input-group-addon" id="cc"><i class="fa fa-users"> </i></span>
<%= l.text_field :cc, hide_label: true, placeholder: "cc:" %>
</div>
<div class="input-group margin-bottom-20">
<span class="input-group-addon" id="bcc"><i class="fa fa-users"> </i></span>
<%= l.text_field :bcc, hide_label: true, placeholder: "bcc:" %>
</div>
<div class="input-group margin-bottom-20">
<span class="input-group-addon" id="subject"> <i class="fa fa-bullhorn"></i></span>
<%= l.text_field :subject, hide_label: true, placeholder: "Subject" %>
</div>
<div class="input-group margin-bottom-20">
<span class="input-group-addon" id="message"> <i class="fa fa-comment"></i></span>
<%= l.text_area :message, hide_label: true, placeholder: "Message", rows:"5" %>
</div>
<%= l.submit "Send", class:'btn-u btn-u-blue btn-block' %>
<% end %>
My controller method in my invoice controller (this is only a show controller, Quote is created in quote model)
def send_invoice_email
quote = Quote.find(params[:invoice])
InvoiceMailer.send_invoice_email(quote, params)
end
And here is the method in my invoice mailer
def send_invoice_email(quote, params)
#quote = quote
mail(
to: current_user.email,
from: params[:from],
content_type: "text/html",
body: params[:body],
content_type: "text/html",
subject: params[:subject],
content_type: "text/html"
)
end
I changed a few things and now it seems to be working... Not sure why but I'll take it. Here are my current configurations..
Form is the same.
In my invoice controller I now have
def send_invoice_email
quote = Quote.find(params[:invoice])
InvoiceMailer.invoice_email(quote, params).deliver_now
end
In my invoice mailer I now have
def invoice_email(quote, params)
#user = quote.user
#quote = quote
#message = params[:body]
mail to: params[:to],
from: #user.email,
body: #message,
subject: params[:subject],
content_type: "text/html"
end

Rails, form_for is updating attribute on all model records, not just the attribute belonging to the instance

I have created a view for volunteers to sign up for specific shifts. I am able to write my view and controller for shifts so that, when the form_for submit button is clicked, the current_user.id is pushed onto the specific shift's user_ids attribute, which is an array. The problem is that it updates each of the shift's user_ids with the current user's id. What am I missing here? Any insight would be appreciated. Thanks.
volunteer.html.erb
<div class="container">
<div style="width:60%;margin:0 auto 0 auto;" class="inner-lower">
<div class="list-group">
<% #uniq_shifts.each do |title| %>
<% #shifts_by_title = #shifts.where(title: title) %>
<% #title_vols = #shifts_by_title.pluck(:vols_needed).sum %>
<% #times = #shifts_by_title.pluck(:time) %>
<% #time_vols = #shifts_by_title.pluck(:vols_needed) %>
<% if #title_vols > 0 %>
<!-- ACTIVITY TITLE -->
<div id=<%= "activity#{title}" %> class="activity">
<a href="#" class="list-group-item">
<%= title %>
<!-- ACTIVITY NUMBER VOLUNTEERS NEEDED-->
<span class="badge-volunteer"><%= #title_vols %></span>
</a>
</div>
<div class="sub" style="display:none;">
<% #shifts_by_title.each do |shift| %>
<!-- ACTIVITY SHIFT -->
<a href="#" class="list-group-item-sub">
<!-- ACTIVITY SHIFT TIME -->
<%= shift.time %>
<span class="badge">
<!-- ACTIVITY SHIFT NUMBER OF VOLUNTEERS NEEDED -->
<%= shift.vols_needed %>
</span>
</a>
<%= form_for shift, :method => :put do |f| %>
<%= f.hidden_field :user_ids, :value => shift.add_user_id(#user.id) %>
<%= f.submit "sign up", class: "btn btn-primary" %>
<% end %>
<% end %>
</div>
<% end %>
<% end %>
</div>
</div>
shift.rb
class Shift < ActiveRecord::Base
has_and_belongs_to_many :users
def add_user_id(user_id)
user_ids_will_change!
update_attributes user_ids: self.user_ids + [ user_id ]
self.save
end
end
shifts.controller.rb
class ShiftsController < ApplicationController
before_action :set_shift, only: [:show, :edit, :update, :destroy]
before_action :volunteer, only: [:show, :edit, :update, :destroy]
def index
#shifts = Shift.all
end
def volunteer
#shifts = Shift.all
#user = current_user
#shift_titles = #shifts.pluck(:title)
#uniq_shifts = #shift_titles.uniq
#vols_needed = #shifts.pluck(:vols_needed)
unless current_user
render action: 'new'
end
end
def show
end
def new
#shift = Shift.new
end
def edit
end
def create
#shift = Shift.new(shift_params)
if #shift.save
redirect_to #shift, notice: 'Shift was successfully created.'
else
render :new
end
end
def update
#user = current_user
if #shift.update(shift_params)
redirect_to #shift, notice: 'Shift was successfully updated.'
else
render :edit
end
end
def destroy
#shift.destroy
redirect_to pages_url, notice: 'Shift was successfully destroyed.'
end
private
# Use callbacks to share common setup or contraints between actions.
def set_shift
#shift = Shift.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def shift_params
params.require(:shift).permit(:title, :time, :vols_needed, :user_ids => [])
end
end

how to set selected value in <%= f.select_tag %> dropdown list, how?

i have a long list dropdown menu including all currencies, i want the last selected value to be the default selected value in the list.
i am using Rails 4.0.0 and ruby 2.0.0
i am not using a model, i am just using a controller. i have put the dropdown list options inside a helper.
form.html.erb
<div class="calculator">
<%= form_for :convertor, :url => {:controller => "converter", :action => "show" } do |f| %>
<%= f.label :from_currency %>
<%= f.select :from_currency, options_for_select(currencies, :selected => params[:from_currency]) %>
<%= f.text_field :amount, :placeholder => "Amount", id: "textfield" %>
<%= #amount %>
<br>
<%= f.label :to_currency %>
<%= f.select :to_currency, options_for_select(currencies, :selected => params[:to_currency].to_i ) %>
<%= #result %>
<br>
<%= f.submit "Calculate", class: "btn btn-large btn-primary", id: "submitButton" %>
<% end %>
</div>
the list options are loaded from a helper *<%= f.select :to_currency, options_for_select(currencies, :selected => params[:to_currency].to_i ) %>* with name currencies
the dropdown list, in a helper
def currencies
[
['United Arab Emirates Dirham (AED)', 'AED'],
['Netherlands Antillean Guilder (ANG)', 'ANG'],
['Argentine Peso (ARS)', 'ARS'],
['Australian Dollar (A$)', 'AUD'],
['Bangladeshi Taka (BDT)', 'BDT'],
['Bulgarian Lev (BGN)', 'BGN'],
['Bahraini Dinar (BHD)', 'BHD'],
]
end
what am i doing wrong here?
A couple of things:
options_for_select takes two arguments: the list of options and the value that you want selected.
You're passing through the value as a hash: :selected => params...
The parameter name is wrong.
In your controller, you're saying that the parameter is called this:
params[:convertor][:from_currency]
But in your view, you have params[:from_currency].
Therefore, the solution to this is to do this:
<%= f.select :from_currency, options_for_select(currencies, params[:convertor][:from_currency]) %>
This code works for me.
in application helper
module ApplicationHelper
def opts_for_select_cur
opts_for_select = [
['United Arab Emirates Dirham (AED)', 'AED'],
['Netherlands Antillean Guilder (ANG)', 'ANG'],
['Argentine Peso (ARS)', 'ARS'],
['Australian Dollar (A$)', 'AUD'],
['Bangladeshi Taka (BDT)', 'BDT'],
['Bulgarian Lev (BGN)', 'BGN'],
['Bahraini Dinar (BHD)', 'BHD'],
]
return opts_for_select
end
end
and in view
<script type="text/javascript">
$(document).ready(function(){
$('#st').change(function(){
var inputText = $('#st :selected').val();
$("#hidden_one").val(inputText);
$("a").attr("href", "?value="+inputText );
});
$('#aa').click(function(){
var inputText = $('#st :selected').val();
$("a").attr("href", "?value="+inputText );
});
});
<%= f.select :text, options_for_select(opts_for_select_cur, params[:value]), {}, { id: "st" } %>
<br>
<%= link_to "refresh",nil, id: "aa" %>
the result is a select box with selected value.
this can help
<%= f.label :from_currency %>
<% if #from_cur.present? %>
<%= f.select :from_currency, options_for_select(currencies, #from_cur) %>
<% else %>
<%= f.select :from_currency, options_for_select(currencies), :required => true %>
<% end %>
<%= f.text_field :amount, :placeholder => "Amount", :required => true, id: "textfield" %>
<br>
<%= f.label :to_currency %>
<% if #from_cur.present? %>
<%= f.select :to_currency, options_for_select(currencies, #to_cur), :required => true %>
<% else %>
<%= f.select :to_currency, options_for_select(currencies), :required => true %>
<% end %>
i appreciate.

Rails 3 form_for helper not submitting action correctly

I have this form:
<% #page_title = "Delete Technician: #{#technician.name}" %>
<%= link_to("<< Back to List", {:action => 'list', :id => #technician.id}, :class => 'back-link') %>
<div class="technician delete">
<h2>Delete Technician</h2>
<%= form_for(:technician, :url => {:action => 'destroy', :id => #technician.id}) do |f| %>
<p>Are you sure you want to permanently delete this technician?</p>
<p class="reference-name"><%= #technician.name %></p>
<div class="form-buttons">
<%= submit_tag("Delete Technician") %>
</div>
<% end %>
</div>
when I click on the submit button this is the url that I get:
www.site.com/technicians/1
instead of
www.site.com/technicians/destroy/1
am I not using the form_for helper correctly or is it a configuration somewhere?
You're making this more complicated than it needs to be. There is no reason for a form when a link or a button would do. Why not just this
<p>Are you sure you want to permanently delete this technician</p>
<div> <%= link_to "Delete", technician_path(#technician), :method => :delete %> </div>
That's it.