Unable to create entry in join table - ruby-on-rails

I'm attempting to save an entry to a join table, but have been unsuccessful. As I have it configured, Stockholder has_many :entity_joins (I'm attempting to allow a Stockholder to consist of multiple people and to allow a Person to participate with multiple stockholders. Each join entry for a single stockholder is added with cocoon as follows:
<h1>Stockholders#edit</h1>
<p>Find me in app/views/stockholders/edit.html.erb</p>
<%= simple_form_for [#stock, #stockholder], html: {id:"stockholderform"}, update: { success: "response", failure: "error"} do |f| %>
<div class="container">
<div class="symegrid">
<div class="form-inline">
<%= simple_fields_for :entity_joins do |ejoin| %>
<%= render 'entity_join_fields', f:ejoin %>
<% end %>
<div>
<%= link_to_add_association 'Add Owner', f, :entity_joins, class: "btn btn-default add-button" %>
</div>
The entity_join_fields just consist of a single field to indicate which person should be added to the Stockholder via a select menu:
<%= f.grouped_collection_select :entity_id, [Org, Person], :all, :model_name, :to_global_id, lambda {|org_or_person_object| org_or_person_object.instance_of? Org? rescue org_or_person_object.fname + " " + org_or_person_object.lname rescue org_or_person_object.name}, label:"Stockholder", class: "names"%>
But when I go to save the Stockholder form, the parameters in the development.log are as follows:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"JF6a/hVU49y1AAX4iAyi6t/Y/ti1S6CAotw28Qj8PGbESpl/puMUliqOz9iwUQ9vHwIeF5hFjykwSUTcKpzGrQ==", "entity_joins"=>{"entity_id"=>"gid://legal/Person/2"}, "stockholder"=>{"cert_number"=>"", "issue_date(2i)"=>"12", "issue_date(3i)"=>"2", "issue_date(1i)"=>"2015", "shares_issued"=>"", "shares_repurchased"=>"", "shares_canceled"=>"", "shares_outstanding"=>""}, "commit"=>"Update Stockholder", "stock_id"=>"1", "id"=>"2"}
The most striking thing about it is that entity_joins falls outside of stockholder params and is not nested within it. Obviously, it isn't saving to the database and no errors are being thrown either. The data for entity_joins is correct (it's the global id for the Person so that I might be able to expand the join to be polymorphic but that's another story). Is there anything obvious that might be causing this issue?
Thanks in advance.

You need to call simple_fields_for on the form builder instance:
<%= f.simple_fields_for :entity_joins do |ejoin| %>
<%= render 'entity_join_fields', f:ejoin %>
<% end %>
Otherwise it does not have a context and will just create inputs with name="entity_joins[something]" instead of stockholder[entity_joins][something].

Related

rails form data entered in the first field is not saved, but is saved when link_to add_nested is used

I'll try to ask this as clearly as I can. I'm doing a Udemy course and we are using nested fields to take in data. The form renders a single nested field initially and then there is a link to add additional fields. This all works and there are no errors in the console. If i put something in the first nested field, and add two more nested fields, only what was entered in the last two nested fields gets saved. When I look at the final result after saving, the first item is not there. I'm sure this has to do with how I'm rendering the initial field, but I can't figure out what it is. Both the initial render and the link_to_add_nested are using the same partial file.
Screenshot of the form
Code from _form.html.erb:
<div>
<%= form_with model: #technologies do |form| %>
<%= render partial: 'technology_fields', :locals => { :form => form } %>
<% end %>
</div>
<div>
<%= link_to_add_nested(form, :technologies, '#Add-Technology', partial: 'technology_fields') %>
</div>
<div class="form-group">
<%= form.submit "Save Portfolio Item", class: 'btn btn-primary btn-block'%>
</div>
<% end %>
</div>
Code from the partial _technology_fields.html.erb:
<div class="form-group nested-fields" id=Add-Technology>
<%= form.label :name %>
<%= form.text_field :name, class: 'form-control' %>
<%= link_to_remove_nested(form, link_text: 'Remove Item') %>
</div>
I can add code from the models and controller if needed, but as the form is working with the dynamically added nested fields, I don't think that's where the problem lies.
Bonus question: The same form is used for new and edit actions. Ideally when the form is used to edit an existing item, I would like the page to render with fields populated with the existing data. As it currently stands I have no way to delete or modify once it has been saved. I can only add new entries.
Thank you very much for any help you can give. I've done a lot of searching but haven't been able to locate the right information.

Rails - Incrementally build join record for nested form

This was the original question I asked but think below adds a bit more detail to the scenario: original question
I'd like to have a form that allows me to incrementally add association records to an object - that also has to be created upon form submission.
Model setup (-< is one to many)
Customer -< CustomerOrder -< OrderLineItem -< Product
In my controller I have a custom route that is:
def custom_order
puts "custom_order called"
#cakeTypes = CakeType.all
#cakes = Cake.all
#customer = Customer.new
#customer.customer_orders.build
end
A customer can only submit one order at a time so that is why I build the customer_orders object. I could include a builder on order_line_items but this is dynamic and what my question is about.
I have a link_to in my form below that has add_to_order - this calls a bit of javascript which sends an ajax request to the controller to grab the relevant details for the order after user selects from a dropdown the product, quantity etc they want.
Am I going in the right direction of "Incrementally Building" (as I describe it) my join records for a nested form for objects that I'd like to have all created at once? How should one do this via the "Rails way"?
My form: html comments added to further describe what I'm trying to do:
<%= form_with(model: customer, local: true, url: "/custom_order") do |form| %>
<!-- customer form fields, name, phone, email-->
<%= render 'customer_fields', f: form %>
<div class="field">
<%= form.fields_for :customer_orders do |builder| %>
<%= builder.label :date_needed %>
<%= builder.date_select :date_needed %>
<br />
<%= builder.label :comments %>
<%= builder.text_area :comments %>
<% end %>
</div>
<!-- order form -->
<div class="field">
<!-- We want to display to user the cake sizes available depending on the cake they select -->
<!-- the cake and size they select should inform the cake_price_id which is a hidden field -->
<!-- {'data-order-line-item-cake' => "#{line_item_builder.options[:child_index]}"} -->
<%= collection_select :order_line_item, :cake_id, Cake.order(:name), :id, :name %>
<%= grouped_collection_select :order_line_item, :cake_size_id, Cake.all, :cake_sizes, :name, :id, :name %>
<%= label_tag :quantity %>
<%= text_field_tag :quantity %>
<%= link_to "Add to order", {}, id: 'add_to_order', remote: true %> <!-- , method: :post, remote: true %> -->
<!-- A "customer" on this page will only every create one order -->
<!-- so each item should field should have -->
<!--customer[customer_orders_attributes][0][date_needed] -->
<!--customer[customer_orders_attributes][0][comments] -->
<!--customer[customer_orders_attributes][0][total] HIDDEN!-->
<div id="order-items">
<% if false %>
<%= text_field_tag "customer[customer_orders_attributes][0][order_line_items_attributes][1][customer_order_id]", "", disabled: true%>
<% end %>
<!-- <input type="text" name="customer[customer_orders_attributes][0][order_line_items_attributes][1][customer_order_id]" id="customer_customer_orders_attributes_0_order_line_items_attributes_1_customer_order_id" value="" disabled="disabled"> -->
<!-- names -->
<!--customer[customer_orders_attributes][0][order_line_items_attributes][0..N][customer_order_id] -->
<!--customer[customer_orders_attributes][0][order_line_items_attributes][0..N][cake_price_id] -->
<!--customer[customer_orders_attributes][0][order_line_items_attributes][0..N][quantity] -->
</div>
<div class="actions">
<%= submit_tag "Submit Order" %>
</div>
<% end %>
My coffeescript - It's a bit hacky at the moment as I'm still learning and also trying things out. I would like to know if what I'm doing so far is correct and if possible how do I go about building the join records dynamically?
$ ->
$('#add_to_order').click ->
console.log("add to order!")
myData =
cake_id: $('#order_line_item_cake_id :selected').val()
cake_size_id: $('#order_line_item_cake_size_id :selected').val()
quantity: $('#quantity').val()
$.ajax
url: "/build_order_item"
type: 'GET'
dataType: 'json'
data: (myData)
success: (data) ->
console.log(data)
#$('#order-items').append("<h6>#{data.quantity} - #{data.size} of #{data.cake} - #{data.price}</h6>")
$('#order-items').append("<input type=\"hidden\" name=\"jims_cake_price_id\" id=\"jims_cake_price_id\" value=#{data.cake_price_id}><br />")
error: ->
alert "Something went wrong"
I've come up with a solution - however I'm still not sure if this is the "Rails Way"
I've had to rely on the dependencies / limitations my app has so far
Namely:
1) a customer can only make one order at a time (there is also no login/authentication to associate users)
Knowing this and from understanding how rails builds id's and names via associations I'm now dynamically constructing the html required to generate the join table elements.
e.g.
<input type="hidden" name="customer[customer_orders_attributes][0][order_line_items_attributes][0][cake_price_id]" value="1">
(Note the first '0' is hardcoded because I know this will be the first order)
The second '0' is generated via javascript. This is the index I re-order on when an element is removed.
i.e.
if customer adds 3 products then the index's will be 0,1,2 - if they remove index 0, then the id's will have to be updated to reflect 0 and 1.
Issues faced with above solution:
I have to reorder the id of elements when one is removed - not so bad because they will always be 0..N-1 for the number of items ordered.
What I'd really really like to know is if I'm going "off the Rails" so to speak... Is what I'm doing above correct? Or is this perfectly acceptable? Or am I being just to worried about doing it the "Rails way" and just go with it - if it works for me?
Any response much appreciated.

Rails multi-record form only saves parameters for last record

I'm trying to offer teachers a form that will create multiple students at once. It seems that most people tackle this concept with nested attributes, but I'm having a hard time understanding how that would work when I'm only using a single model. This article made it seem possible to achieve this without nested attributes, but my results are not working the way the author suggests. The students array should include one hash for each section of the form. But when I submit the form and check the parameters, only one single hash exists in the array.
Adjusting her approach, I've got this controller:
students_controller.rb
def multi
#student_group = []
5.times do
#student_group << Student.new
end
end
(I'm using an action I've called "multi" because it's a different view than the regular "create" action, which only creates one student at a time. I've tried moving everything into the regular create action, but I get the same results.)
The view:
multi.html.erb
<%= form_tag students_path do %>
<% #student_group.each do |student| %>
<%= fields_for 'students[]', student do |s| %>
<div class="field">
<%= s.label :first_name %><br>
<%= s.text_field :first_name %>
</div>
<div class="field">
<%= s.label :last_name %><br>
<%= s.text_field :last_name %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= submit_tag %>
</div>
<% end %>
The results:
(byebug) params
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"3Xpi4XeqXuPs9jQvevy+nvGB1HiProddZzWq6Ed7Oljr3TR2fhx9Js6fN/F9xYcpgfDckCBOC2CoN+MrlFU0Bg==", "students"=>{"first_name"=>"fff", "last_name"=>"ggg"}, "commit"=>"Save changes", "controller"=>"students", "action"=>"create"} permitted: false>
Only one has is included for a student named "fff ggg". There should be four other hashes with different students.
Thank you in advance for any insight.
fields_for is only used in conjunction with form_for. The for is referring to a model, which it expects you to use. Since you're trying to build a form with no model, you have to construct your own input field names.
Don't use fields_for but instead, render each input using the form tag helpers e.g.
<%= label_tag "students__first_name", "First Name" %>
<%= text_field_tag "students[][first_name]" %>
...and so on.
The key is that the field names have that [] in them to indicate that the students parameters will be an array of hashes. You almost got it by telling fields_for to be called students[] but fields_for ignored it because it needs a model to work correctly.

Nested fields not being added on form submit

I am using the cocoon gem to try and achieve adding an object which belongs to another with nested fields. I have a 'user_resolution' which has many 'milestones'. I have set up the associations accordingly in both of these models. For some reason, milestones are failing to be created, however if I add one manually in the database I can successfully update it. I am able to dynamically add the fields and remove them using the cocoon gem but that is all. When I click 'add milestone' it redirects me to the show view of the user resolution and throws the success message saying user resolution has been updated, no errors are thrown but the milestone(s) is/are not created.
user_resolution.rb
has_many :milestones
accepts_nested_attributes_for :milestones, reject_if: :all_blank, allow_destroy: true
milestone.rb
belongs_to :user_resolution
I have set up the nested form within the edit view as for now I only want users to add a milestone to a resolution in the edit view.
user_resolutions/edit.html.erb
<%= form_for(#user_resolution) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<%= f.fields_for :milestones do |milestone| %>
<%= render 'milestone_fields', f: milestone %>
<% end %>
<%= link_to_add_association 'Add Milestone', f, :milestones %>
<%= f.submit "Add Milestone" %>
<% end %>
_milestone_fields.html.erb
<div class="nested-fields">
<div class="field-row">
<%= f.label :name, 'Name' %>
<%= f.text_field :name %>
</div>
<div class="field-row">
<%= f.label :description, 'Name' %>
<%= f.text_area :description %>
</div>
<div class="field-row">
<%= f.label :severity, 'severity' %>
<%= f.check_box :severity %>
</div>
<div class="field-row">
<%= f.label :target_date, 'target_date' %>
<%= f.date_select :target_date %>
</div>
<%= link_to_remove_association 'Remove', f %>
</div>
The permitted parameters within the user resolutions controller also contain the following
milestones_attributes: [:id, :user_resolution_id, :name, :description, :target_date, :severity, :complete, :_destroy]
The milestones themselves have no views, they only have a model and a controller. The controller create action (which i'm unsure is required for nested forms) contains the standard following code
def create
#milestone = Milestone.new(milestone_params)
if #milestone.save
redirect_to user_resolutions_path,
:flash => { :success => "You successfully created a milestone" }
else
redirect_to new_milestone_path,
:flash => { :error => "Oops something went wrong. Try again." }
end
end
I've been as informative as I can but if you need anything else let me know. Thanks guys.
which i'm unsure is required for nested forms
You don't need a create action for milestones - they'll be populated from the user_resolutions#create controller action.
There are several things to look at with this. I'll detail some here. This won't be a specific answer, but may help point you in the right direction.
Firstly, you need to make sure you're receiving the correct params.
Cocoon does a great job building the nested form - you need to make sure it's obliging Rails' nested attribute structure.
To do this, you should right-click > view source.
In the f.fields_for section (it won't be called that in the HTML), you'll be looking for the equivalent to the following:
<input type="text" name="milestones_attributes[0][name]" value="">
The important thing to note is the name...
Each time you use a form, or any Rails view helper for that matter, you're really just building standard HTML. form_for just creates an HTML form, and thus any params contained within it need to adhere to a certain structure for Rails to recognize the params.
The f.fields_for elements will typically be called x_attributes[:id][:param] - this is passed to Rails, which cycles through each [:id] to determine the number of nested params to add.
You need to check the source for the above naming structure. If you see it, that's good. If not, it means you haven't built your form properly.
Secondly, you need to make sure your objects are being built in the controller.
I'm not sure how Cocoon does this, but essentially, each time you use f.fields_for, you have to build the associated object before:
def new
#user_reservation = UserReservation.new
#user_reservation.milestones.build #-> this is what makes f.fields_for work
end
If the first step shows incorrect element naming, it means your associative objects are not being built (which is why they're not being recognized).
To test it, you should build the associative objects in the new method, before sending.
Finally, you'll want to post your params.
These tell you in explicit detail what Rails is doing with the nested attributes, allowing you to determine what's happening with them.
Sorry for the long-winded answer. You'll not have received any answers anyway, so I felt it prudent to give you something.

Ruby on Rails: assign relationship on creation

I'm new to Ruby on Rails. There are two models in my project: room and guest. The association is "room has_many guests" and "guest belongs to room".
I have separated views for manage rooms and guests. Rooms don't require "guests" value on creation. However, I want to create new guests and assign it to certain room at the same time. What will be the proper way to do it? How do I transfer the input from web and match the entities in database.
The code is pretty much the same as "Getting Started with Rails". In the tutorial, they add "comments" in the "article" view and use "comment" as a sub-resource of "article". In my case, I treat the two models equally and want to manage them in separated views.
Update:
I used the collection_select and try to work with my guest_controller.
<%= form_for :guest, url: guests_path do |f| %>
<% if #guest.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#guest.errors.count, "error") %> prohibited this guest from being added:
</h2>
<ul>
<% #guest.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :phone %><br>
<%= f.text_field :phone %>
</p>
<p>
<%= f.label :room%><br>
<%= f.text_field :room %>
</p>
<p>
<%= f.label :room %><br>
<%= f.collection_select(:room_id, Room.all, :id, :title) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', guests_path %>
In my guest_controller, the create method called by the form above is :
def create
#guest = Guest.new(guest_params)
#guest.room = Room.find(params[:room_id])
if #guest.save
redirect_to #guest
else
render 'new'
end
end
However, when I create a new guest, it shows that:
ActiveRecord::RecordNotFound in GuestsController#create
Couldn't find Room with 'id'=
I checked that room_id=4 and Room.find(4) return the proper room.
What's wrong?
If you want to select one room from those that exist, use collection_select form helper, here is a relevant snippet from the docs:
f.collection_select(:city_id, City.all, :id, :name)
This outputs a dropdown list that:
fills in city_id parameter in this context
uses City.all for filling in the options in the list (I will be referring to "each" city as city)
uses city.id as data (that gets sent in the form)
shows city.name for each city in the dropdown list (hopefully, human-readable)
Bear in mind though, that in terms of security it's like "look, you can select this, and this and this!", that does not prevent users from selecting an unlisted option: either by modifying form markup by hand or sending handcrafted queries.
So should you ever be limiting access to specific rooms, and list only Room.unlocked (unlocked assumed a scope), make sure the received room_id refers to a room from that scope as well. Most of these problems are dealt with using either validations or careful association management (Room.unlocked.find_by_id(:room_id) that outputs nil if the room is not in that scope).
UPD: as for the latest problem you're having -- your understanding on how the form contents look in params seems to be wrong. It's quite a common misconception actually.
form_for :guest will construct a separate object/hash in params[:guest], with all the form's fields inside it. So it actually is inside params[:guest][:room_id], but no, don't rush with adding the missing part.
You've already built a #guest object from entire params[:guest], so if the room actually exists, it's inside #guest.room already and can be validated inside the model during save. Have a look at Rails validators.
Take a look at the fields_for tag:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
It allows just that, to create a guest while creating a room and associating each other.

Resources