there, I'm coming back to Rails after years of only using it for APIs. There all sorts of new things and I'm trying to figure out how to accomplish some stuff with the new frameworks. One example is creating a form with accept_nested_attributes and adding dynamic has_many associations.
I have Company model and Partner models.
class Company < ApplicationRecord
# ... name, size, registration_type, etc
has_many :partners, dependent: :destroy
accepted_nested_attributes_for :partners
end
class Partner < ApplicationRecord
# ... name, email, phone
belong_to :company
end
In my form I have:
<%= form_with(model: company) do |form} %>
<!-- ... -->
<div class="hidden" data-company-form-target="partnersForm">
<%= form.fields_for :partners, company.partners do |partner_form| %>
<div data-company-form-target="partnerFormInner">
<div class="form-group">
<div>
<%= partner_form.label :name, "Name" %>
<%= partner_form.text_field :name %>
</div>
<div>
<%= partner_form.label :email, "Email" %>
<%= partner_form.text_field :email %>
</div>
<div>
<%= partner_form.label :phone_number, "Phone Number" %>
<%= partner_form.text_field :phone_number %>
</div>
</div>
</div>
<% end %>
<div>
<button data-action="click->company-form#addPartner">
+
</button>
</div>
</div>
<% end %>
I have a js controller that uses the button to add a new line to the form to add more partners:
import { Controller } from "stimulus";
export default class extends Controller {
static targets = [ "partnerFormInner" ]
addPartner(e) {
e.preventDefault(); e.stopPropagation();
this.partnerFormInnerTarget.insertAdjacentHTML('beforeend', this.formTemplate)
this.count++
}
connect() {
this.count = 1
}
get formTemplate() {
return `<div>
<div>
<label for="company_partners_attributes_${this.count}_name">Name</label>
<input type="text" name="company[partners_attributes][${this.count}][name]" id="company_partners_attributes_${this.count}_name">
</div>
<div>
<label for="company_partners_attributes_${this.count}_email">Email</label>
<input type="text" email="company[partners_attributes][${this.count}][email]" id="company_partners_attributes_${this.count}_email">
</div>
<div>
<label for="company_partners_attributes_${this.count}_phone_number">Phone Number</label>
<input type="text" phone_number="company[partners_attributes][${this.count}][phone_number]" id="company_partners_attributes_${this.count}_phone_number">
</div>
</div>`
}
}
Now this all works fine, however I feel like the js controller is a little hacky, copying the HTML output from a single partner and pasting it into my controller w/ some interpolation... I feel like there's probably some way for me to get that template directly from the Rails backend so that I have it all defined in one place in a partial or something, but I'm not sure how to connect those dots.
Is there a way to move the partner form to a partial and dynamically pull code for the next line in the form and insert it via JS, or do I need to just keep doing what I'm doing with the copy/paste?
Related
In my Rails 5 app I have a Simple Form form for creating or updating for my model Training. In this form I already have checkboxes for each of my Participants to assign them to the Training. Now I'm trying to add a radio button group (5 for a rating from 1 to 5) for each Participant, because each can rate the Training.
Screen shot of the rendered form
I managed to render the radio buttons as I wanted but unfortunately I can only select one of all the radio buttons rendered. They are even strangely connected to the checkboxes, which will probably be because they have the same input method participant_ids, which is connected to the has_and_belongs_to_many association with Participant :participants.
<%= simple_form_for #training do |f| %>
...
<div id="participants-ratings-container">
<div id="participants-container" style="display: inline-block; vertical-align: top; width: 8em">
<%= f.label t('activerecord.models.participant') %>
<%= f.collection_check_boxes :participant_ids, Participant.all.order(:name), :id, :name, {:item_wrapper_class => 'checkbox-container'} %>
</div>
<div id="ratings-container" style="display: inline-block; vertical-align: top">
<%= f.label t('trainings._form.ratings') %>
<% Participant.all.order(:name).each do |p| %>
<%= f.input :participant_ids, collection: 1..5, as: :radio_buttons, label: false%>
<% end %>
</div>
...
<% end %>
I tried a lot of things but I have no ideas anymore. If it is possible I would like to fill a Hash participant_ratings in my Training class in the way "participant.name" => rating value.
Can anyone give me a hint on how to achieve this? Maybe even tell me how to disable the radio button group if the corresponding Participant is not checked?
Thanks in advance!
UPDATE
I managed to adapt my form and even integrate jQuery raty plugin to enter the rating value for each Participant. Each rating is sent with it's corresponding participant-id to the server as a hash, e.g. like so:
"participant_ratings"=>{"3"=>"5", "1"=>"3.5", "2"=>""}.
Every rating is disabled and it's value set to 0 if the corresponding participant checkbox isn't checked. Here is my view code:
<div id="ratings-container" style="display: inline-block; vertical-align: top">
<%= f.label t('trainings._form.ratings') %>
<% Participant.all.order(:name).each do |p| %>
<span class="checkbox_container">
<% current_rating = #training.participant_ratings[p.id].nil? ? '' : #training.participant_ratings[p.id] %>
<label id='star<%= "#{p.id}" %>' data-rating='<%= "#{current_rating}" %>' style="margin-bottom: 12px;" ></label>
<input id="rating<%= "#{p.id}" %>" name="participant_ratings[<%= "#{p.id}" %>]" type="hidden" value="<%= "#{#training.participant_ratings[p.id]}" %>" />
</span>
<script>
$('#star<%= "#{p.id}" %>').raty({
score: function() {
return $(this).attr('data-rating');
},
half : true,
readOnly: <%= !#training.participants.include?(p) %>,
path: '/assets',
click : function(score, evt) {
$('#rating<%= "#{p.id}" %>').val(score);
}
});
$('#training_participant_ids_<%= "#{p.id}" %>').change(function () {
var star = $('#star<%= "#{p.id}" %>');
if(document.getElementById('training_participant_ids_<%= "#{p.id}" %>').checked) {
star.raty('readOnly', false);
} else {
star.raty('cancel');
star.raty('readOnly', true);
$('#rating<%= "#{p.id}" %>').val("");
}
})
</script>
<% end %>
</div>
New screen shot of the rendered form
The last thing I don't seem to be able to figure out is how to save the participant_ratings hash in the corresponding model attribute :participant_ratings
If I allowed the param :participant_ratings in my trainings_controller.rb but nothing was saved. So I added
#training.participant_ratings = params[:participant_ratings]
and now I get the error
Attribute was supposed to be a Hash, but was a ActionController::Parameters. -- <ActionController::Parameters {"3"=>"5", "1"=>"3.5", "2"=>""} permitted: false>
The methods in my trainings_controller.rb:
class TrainingsController < BaseController
...
def update
#training = Training.find(params[:id])
#training.updated_date_time = DateTime.now
#training.participant_ratings = params[:participant_ratings]
if #training.update(training_params)
flash[:notice] = t('flash.training.updated')
redirect_to #training
else
render 'edit'
end
end
private
def training_params
params.require(:training).permit(:village, :topic, :user_id, :start_time, :end_time, {:participant_ids => []}, :participant_ratings)
end
end
How do I save the hash sent by the form in the model's attribute? Where am I going wrong?
Can I somehow define participant_ratings as a hash in the params similar to the array participant_ids there?
Firstly, I had to make sure that the participants_rating-hash was inside the training-hash when submitted by my form, so I had to change the name attribute in my hidden form input for the rating:
<input id="rating<%= "#{p.id}" %>" name="training[participant_ratings][<%= "#{p.id}" %>]" type="hidden" value="<%= "#{#training.participant_ratings[p.id]}" %>" />
Secondly, I had to whitelist the keys for my participant_ratings-hash so that they are permitted:
def training_params
ratings_keys = params[:training].try(:fetch, :participant_ratings, {}).keys
params.require(:training).permit(:village, :topic, :user_id, :start_time, :end_time, {:participant_ids => []}, participant_ratings: ratings_keys )
end
I found this solution in this rails thread
After that participant_ratings was saved just fine!
Rails 3.2
This is my second attempt at solving this problem.
I inherited an application for processing customer tickets. The main ticket view, is large and complicated, so it's split up into multiple sections.
I am trying to add a new section to enter customers information.
I have a scaffolding for CustomerInfo, with a controller and a model, in the normal locations:
controllers/customer_infos_controller.rb and models/customer_info.rb.
I put the views under views/tickets/sections
In my views/tickets/show.html.slim, I have:
- #customer_info = customer_info #ticket
h4.form-header Customer Information
.form-section.attachments
- if #customer_info.nil?
= render partial: 'tickets/sections/customer_info', locals: {ticket: #ticket }
In my , views/tickets/sections/_customer_info.html.slim, I have:
= form_for(CustomerInfo.new, url: customer_info_path) do |f|
- f.hidden_field :ticket_id, :value => ticket.id
.form-horizontal-column.customer-info
.form-group
= f.label :first
= f.text_field :first, maxlength: 50
.form-group
= f.label :last
= f.text_field :last, maxlength: 50
.actions = f.submit 'Save'
.clear
In my routes.rb, I have:
post '/customer_infos' => 'customer_infos#create', as: 'customer_info'
When I run the application, the form displays properly. But, when I enter the first name, and last name, the ticket gets updated.
Looking at the log file, I see that when the Save button in the Customer Information section, I see:
Processing by TicketsController#update as HTML
In the log, I also saw the params:
{
"ticket":
{
.......................
},
"customer_info":
{
"first":"John",
"last":"Doe"
},
"commit":"Save"
}
rake routes gives me:
customer_info POST /customer_infos(.:format) customer_infos#create
This is the HTML:
<div class="form-horizontal-column customer-info">
<div class="form-group">
<label for="customer_info_first">First *</label>
<input id="customer_info_first" type="text" size="50" name="customer_info[first]" maxlength="50">
</div>
<div class="form-group">
<label for="customer_info_last">Post tax total *</label>
<input id="customer_info_last" type="text" size="50" name="customer_info[last]" maxlength="50">
</div>
<div class="actions">
<input type="submit" value="Save" name="commit">
</div>
</div>
This is the portion:
<form accept-charset="UTF-8" action="http://xxx.xxxx.xxxx.com/admin/tickets/159384" class="form form-horizontal tickets-form context-form" data-admin="true" enctype="multipart/form-data" id="edit_ticket_159384" method="post">
So, there is no customer_info portion, with the proper action and method.
Why is the tickets controller update method is invoked, instead of the CustomerInfo create method? I also don't see the ticket_id param in the params.
I'm using Rails 4.2.3 with MySql 5.5.37. I recently change the name and type of my column and now when I submit my Rails form that form field, "selection_id," is not getting captured. Below is my Rails form ...
<div class="field">
<%= f.label :day %><br>
<%= f.hidden_field :day, :validate => true %>
<div id="datepicker"></div>
</div>
<div class="field">
<%= f.label :selection_id %><br>
<div class="styled-select"><%= collection_select(:user_selection, :selection_id, #selections, :id, :description, {:prompt => true}) %></div>
</div>
<div class="field">
<%= f.label :total %><br>
<%= f.text_field :total, :size => 4, :validate => true %>
</div>
And here is the HTML that gets output to the browser ...
<div class="field">
<label for="user_selection_day">Day</label><br>
<input validate="true" type="hidden" value="02/07/2016" name="user_selection[day]" id="user_selection_day" />
<div id="datepicker"></div>
</div>
<div class="field">
<div class="field_with_errors"><label for="user_selection_selection_id">selection</label></div><br>
<div class="styled-select"><div class="field_with_errors"><select name="user_selection[selection_id]" id="user_selection_selection_id"><option value="">Please select</option>
<option value="3">option 1</option>
<option value="4">option 2</option></select></div></div>
</div>
<div class="field">
<label for="user_selection_total">Total</label><br>
<input size="4" validate="true" type="text" value="1" name="user_selection[total]" id="user_selection_total" />
</div>
I can see this data getting submitted in Chrome ...
user_selection[day]:02/19/2016
user_selection[selection_id]:3
user_selection[total]:9
but on my controller side, when I try and output the params, only two of the three are printing out. This "puts"
def create
#current_user = User.find(session["user_id"])
...
puts user_selection_params
prints
{"day"=>"02/19/2016", "total"=>"9"}
Why is this other field getting lost and how can I fix it?
You are having this problem because #object_id is a method defined on Object, the class that all Ruby objects extend. In other words, it's reserved by Ruby itself and can't be used when naming database fields / model attributes. You're going to have to rename that column/association to something else.
As a side note, it's not idiomatic Ruby to include the word "Object" in your class name. Arguably the class should just be named User, instead of UserObject.
Reference: http://ruby-doc.org/core-2.3.0/Object.html#method-i-object_id
Please don't use object_id in your model.
There should be no column named object_id in the database.
object_id is a default methods that all (except BasicObject in Ruby 1.9) objects have ...
(see docs).
I am using validates_acceptance_of :terms, :message => "must be accepted" in my user.rb model, and am using bootstrap-sass.
My check box code looks like this in the view:
<div class="control-group">
<%= f.label :terms, :class => "control-label" do %>
Accept <%= link_to('Terms of Use *', "#myTOUModal", :"data-toggle" => "modal") %>
<% end %>
<div class="controls">
<%= f.check_box :terms %>
</div>
</div>
For some reason, when the terms check box isn't selected on form submission, the appropriate error message shows up at the top of the form, but there is a problem with the field_with_errors div class wrapping around the check box label.
The HTML for the rendered page looks like this:
<div class="control-group">
<label class="control-label" for="user_terms">
Accept Terms of Use *
</label>
<div class="controls">
<input name="user[terms]" type="hidden" value="0" />
<div class="field_with_errors">
<input id="user_terms" name="user[terms]" type="checkbox" value="1" />
</div>
</div>
</div>
The result is that the check box field label isn't highlighted on error. Is there a way to force the div tag placement for the field_with_errors class to show up just after the <div class="control-group"> tag? Why does using a block to define a field label throw off the field_with_errors tag placement? Does anyone have experience with this?
Thank you
This is a bug i think. The problem is in block. Define your label without block and everything works.
Try something like:
<% modal_html = capture do >
Accept <%= link_to('Terms of Use *', "#myTOUModal", :"data-toggle" => "modal") %>
<% end %>
<%= f.label :terms, modal_html, :class => "control-label" %>
Or helper:
def modal_html
#Q{Accept #{link_to('Terms of Use *', "#myTOUModal", :"data-toggle" => "modal")} }.html_safe
end
I need to create a multi-edit form in rails, like so:
<form>
<input type='text' name='input1'></input>
<input type='text' name='input2'></input>
<input type='text' name='input3'></input>
<input type='text' name='input4'></input>
<input type='text' name='input5'></input>
<br>
<input type='text' name='input1'></input>
<input type='text' name='input2'></input>
<input type='text' name='input3'></input>
<input type='text' name='input4'></input>
<input type='text' name='input5'></input>
<br>
<input type='text' name='input1'></input>
<input type='text' name='input2'></input>
<input type='text' name='input3'></input>
<input type='text' name='input4'></input>
<input type='text' name='input5'></input>
<br>
... and so on, then the "<submit>" button will be at the very end. One click of the submit button at the end should collect all the values and parse them in the controller.
I just need to know how to generate the multi-edit form in the view. Also, each row is unique; I'd also need to know how to assign a unique identifier to each of the input tags I guess; I do have a unique ID value I could use.
This is trivial to accomplish, but we need more information. How are these fields related to your models? Is this one model with many fields, many instances of a model or something else?
What you want to do in this situation is use a form builder. It will generate input fields according to a naming convention that will be parsed into a much more useful format when it gets to the controller. Since I have no information about your models, I will use a hypothetical example:
class Post < ActiveRecord::Base
attr_accessible :title, :body, :author, :published_at
end
Create the form using the form_for helper. It will give you a formbuilder object to create the input fields.
<% form_for :post do |f| -%>
<p>
<%= f.label :title %>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %>
<%= f.text_area :body %>
</p>
<p>
<%= f.label :author %>
<%= f.text_field :author %>
</p>
<p>
<%= f.label :published_at %>
<%= f.datetime_select :published_at %>
</p>
<% end -%>
The key benefit of using helpers is the name attribute of the inputs it generates. Since body belongs to a form for post it will be given the name attribute post[body]. These attributes will be parsed into the following hash:
:post => {
:title => "This is the title",
:body => "this is the body",
:author => "John Doe",
:published_at => "Mon Nov 15 2010 19:23:40 GMT-0600 (CST)"
}
This means you don't need to manually copy fields into a model. You can just pass it directly to the Model#new method:
#post = Post.new(params[:post])
and then do your validation checks. This convention becomes indispensable when you start nesting models inside one another.
See here for a more thorough guide to form helpers.