Rails Custom Nested Update not updating - ruby-on-rails

My app has an Animal model which has many (and accepts nested attributes for) Packages. In my Animals Controller, I created the following action to allow for the editing of certain Animal (and nested Package) attributes in a separate page (and for a separate user role) from that used for normal Animal editing. (To clarify, I have an animals#edit action and page, and a separate animals#log action and page, which ideally allow different user roles to edit different things about the animal):
def log
#animal = Animal.find(params[:animal_id])
if params[:animal]
if #animal.update_attributes(params[:animal])
# I want a basic reload (with flash) in cases of both success and failure.
redirect_to log_path(#animal), notice: "Animal update successful!"
else
redirect_to log_path(#animal), notice: "Update unsuccessful. Contact admin"
end
end
end
My separate edit/update actions:
def update
#animal = Animal.find(params[:id])
if #animal.update_attributes(params[:animal])
redirect_to #animal, notice: "Animal was successfully updated."
else
render action: "edit"
end
end
def edit
#animal = Animal.find(params[:id])
end
The problem is, when I hit my submit button in my log page (code below), it refreshes the page, but a) without any notice of any sort (minor problem), and b) more importantly, without actually updating the package information. Importantly, if I update the animal.weight attribute (the only ANIMAL attribute modifiable here), THAT works. Just not the nested package attributes.
Help? I'm really stumped here. I've been fiddling with this forever trying to get it to work. Some possibly relevant code below:
In my routes.rb file (above animal resources):
match '/animals/:animal_id/log' => "animals#log", as: :log
Here's my log view:
<%= form_for(#animal, :url => { :action => "log" }, :method => :put) do |f| %>
<div style="width: 200px">
<% if #animal.weight %>
<%= "Weight: #{#animal.weight}lbs" %>
<% else %>
<%= f.label "Weight (in lbs)" %>
<%= f.text_field :weight %>
<% end %>
</div>
# I'm cycling through each bundle to group "identical" packages in a table (one per bundle).
<% #animal.sold_bundles.each do |bundle| %> <!-- Packages are bundled by cut and prep notes -->
<%= render partial: 'package_bundle', locals: {:bundle => bundle, :f => f} %><br>
<% end %>
<%= f.submit "Submit Animal Log", :class => "btn image-button right" %>
<% end %>
And here's the "package_bundle" partial called for each bundle. It produces a table of packages, some editable (because of blank true_weight value) and some not:
<table class="table">
<thead>
<tr>
<th>Cut Name</th>
<th>Prep Notes</th>
<th>Label</th>
<th>Actual Lbs</th>
<th>Actual Oz</th>
</tr>
</thead>
<tbody>
<!-- list of specified packages -->
<%= f.fields_for :packages, bundle do |p| %>
<tr>
<td><%= p.object.name %></td>
<td><%= "#{p.object.user.name.first(3).upcase}-#{p.object.id}" %></td>
<% if p.object.true_weight == nil || p.object.true_weight == 0 %>
<td colspan=1><%= p.text_field :actual_lbs %></td>
<td colspan=1><%= p.text_field :actual_oz %></td>
<% else %>
<td><%= p.object.true_weight.round(0) %> lb</td>
<td><%= ((p.object.true_weight % 1) * 16).round(2)%> oz </td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
EDIT -- More code on request
package.rb (just what's relevant) -- I evaded the below gotcha (this time; I've definitely made that mistake before). It's a possibility that my method for converting lbs/oz to lbs with percent is to blame here, but it looks right to me:
class Package < ActiveRecord::Base
attr_accessible :animal_id, :cut_id, :price, :line_id, :sold, :savings, :actual_lbs, :actual_oz, :true_weight
attr_accessor :actual_lbs, :actual_oz
belongs_to :animal
belongs_to :cut
belongs_to :line
before_update :to_true
def to_true
if self.sold
if self.order.status > 1 # This is the case for any package loaded on the log page
if self.actual_lbs && self.actual_oz
unless self.true_weight && self.true_weight > 0 # Don't overwrite legit true weights
percent = self.actual_oz / 16
self.update_attribute(:true_weight, self.actual_lbs + percent)
end
end
end
end
end
animal.rb (only relevant):
class Animal < ActiveRecord::Base
attr_accessible :breed, :name, :photo, :animal_type, :hanging_weight, :meat_weight,
:weight, :ranch_id, :butcher_id, :cow_mult, :pig_mult, :packages_attributes,
:lamb_mult, :goat_mult, :host_id, :final_sale, :opening_sale, :open, :no_sales
has_many :orders
has_many :packages
belongs_to :butcher
belongs_to :ranch
belongs_to :host
after_create :create_packages
accepts_nested_attributes_for :packages
Looked at my server log (easier for me to understand than the dev log), to get to the bottom of this, and notice two weird things. There's no error at all, but 1) It seems to execute the GET for the page when I load it, but not the requested PUT when I hit submit, and 2) Probably as a consequence, the only param passed (which is passed as part of the GET request) is the animal id; no package attributes are passed.

Can you post your model code? A common "gotcha" that comes to mind is not adding, in your case, package_attributes to the attr_accessible on the animal model. It will not raise an error, but tucked into the development log, prior to the SQL entries, you will see the mass-assignment warning.

attr_accessor won't trigger callbacks. You have to make a variable "dirty". More info here.
In your package.rb:
def actual_lbs=(val)
true_weight_will_change!
#actual_lbs=val
end
This will make it such that when it sets the actual_lbs variable, it will "dirty" a variable that is in the database, thus firing the callbacks.

Related

How to chain search params and use them as objects?

In my project I have a department model. I want to add employees to the department by using a search. I want to add the result of the search to a list, then submit the list and add all searched employees in one go at the end, all in the same view.
Search function in departments_controller
def add_employees
employees = Employee.all
#searched_employee = Employee.where('name LIKE ?', "#{params[:search_by_name]}")
#searched_employee.each do |employee|
#searched_employee_name = employee.name
end
end
add_employees-view:
h1 Add employees
= form_for #department, :url => add_employees_path(:param1 => #searched_employee_name, :param2 => request.query_parameters), method: :post do
= label_tag :search_by_name
br
= search_field_tag :search_by_name, params[:name]
= submit_tag "Search"
= form_for #department, :url => add_employee_path, html: {method: "post"} do |f|
- if params[:search_by_name].present?
- #searched_employee.each do |employee|
li = employee.name
br
table
h5 Employees
thead
tr Name
tr Email
tbody
- #searched_employee.each do |employee|
tr
td = employee.name
td = request.query_parameters
Single search works fine, so I hoped to add a second param which stores the first request to be passed on for the next search and so forth.
Now I am stuck with splitting up the long query string into its unique search results and their objects, as to add them to a list where I can then work further with them (checkboxes etc).
Request.query_parameters is nested, but does not react to dig, because it says it is a string.
Any ideas on how to approach this or maybe a better solution, without the use of additional gems?
Here is how I would solve it if I had to do it without JS.
Create an M-2-M association with a join model between deparments and employees. Let departments accept nested attributes for the join model:
class Department < ApplicationRecord
has_many :positions
has_many :employees, through: :positions
accepts_nested_attributes_for :positions, reject_if: :reject_position?
private
def reject_position?(attributes)
!ActiveModel::Type::Boolean.new.cast(attributes['_keep'])
end
end
class Employee < ApplicationRecord
has_many :departments
has_many :positions, through: :departments
end
# rails g model position employee:belongs_to department:belongs_to
class Position < ApplicationRecord
belongs_to :employee
belongs_to :department
attribute :_keep, :boolean
end
Setup the routes:
# config/routes.rb
Rails.application.routes.draw do
# ...
# #todo merge this with your existing routes
resources :departments, only: [] do
resources :employees, only: [], module: :departments do
collection do
get :search
patch '/',
action: :update_collection,
as: :update
end
end
end
end
Now lets create the search form:
# /app/views/departments/employees/search.rb
<%= form_with(
url: search_department_employees_path(#department),
local: true,
method: :get
) do |form| %>
<div class="field">
<%= form.label :search_by_name %>
<%= form.text_field :search_by_name %>
</div>
<% #results&.each do |employee| %>
<%= hidden_field_tag('stored_employee_ids[]', employee.id) %>
<% end %>
<%= form.submit("Search") %>
<% end %>
Note that we are using GET instead of POST. Since this action is idempotent (it does not actually alter anything) you can use GET.
Note <%= hidden_field_tag('stored_employee_ids[]', employee.id) %>. Rack will merge any pairs where the key ends with [] into an array.
Now lets setup the controller:
module Departments
# Controller that handles employees on a per department level
class EmployeesController < ApplicationController
before_action :set_department
# Search employees by name
# This route is nested in the department since we want to exclude employees
# that belong to the department
# GET /departments/1/employees?search_by_name=john
def search
#search_term = params[:search_by_name]
#stored_ids = params[:stored_employee_ids]
if #search_term.present?
#results = not_employed.where('employees.name LIKE ?', #search_term)
end
# merge the search results with the "stored" employee ids we are passing along
if #stored_ids.present?
#results = not_employed.or(Employee.where(id: #stored_ids))
end
#positions = (#results||[]).map do |employee|
Position.new(employee: employee)
end
end
private
def not_employed
#results ||= Employee.where.not(id: #department.employees)
end
def set_department
#department = Department.find(params[:department_id])
end
end
end
This just creates a form that "loops back on itself" and just keeps adding more ids to the query string - without all that hackery.
Now lets create a second form where we actually do something with the search results as a partial:
# app/views/departments/employees/_update_collection_form.html.erb
<%= form_with(
model: #department,
url: update_department_employees_path(#department),
local: true,
method: :patch
) do |form |%>
<legend>Add to <%= form.object.name %></legend>
<table>
<thead>
<tr>
<td>Add?</td>
<td>Name</td>
<tr>
</thead>
<tbody>
<%= form.fields_for(:positions, #positions) do |p_fields| %>
<tr>
<td>
<%= p_fields.label :_keep, class: 'aria-hidden' %>
<%= p_fields.check_box :_keep %>
</td>
<td>
<%= p_fields.object.employee.name %>
<%= p_fields.hidden_field :employee_id %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= form.submit 'Add employees to department' %>
<% end %>
form.fields_for(:positions, #positions) loops through the array and creates inputs for each position.
And render the partial in app/views/departments/employees/search.html.erb:
# ...
<%= render partial: 'update_collection_form' if #positions.any? %>
You should not nest this form inside another form. That will result in invalid HTML and will not work properly.
Unlike your solution I'm not cramming everything and the bathtub into a single endoint. This form sends a PATCH request to /departments/1/employees. Using PATCH on an entire collection like this is somewhat rare as we usually just use it for individual members. But here we really are adding a bunch of stuff to the collection itself.
Now lets add the action to the controller:
module Departments
# Controller that handles employees on a per department level
class EmployeesController < ApplicationController
# ...
# Adds a bunch of employees to a department
# PATCH /departments/:department_id/employees
def update_collection
if #department.update(nested_attributes)
redirect_to action: :search,
flash: 'Employees added'
else
#postions = #department.positions.select(&:new_record?)
render :search,
flash: 'Some employees could not be added'
end
end
private
# ...
def update_collection_attributes
params.require(:department)
.permit(
positions_attributes: [
:keep,
:employee_id
]
)
end
end
end
There is almost nothing to it since accepts_nested_attributes is doing all the work on the controller layer.
I'll leave it up to you to convert this ERB to Slim or Haml and adapt it to your existing code base.
So, if anyone faces a similar problem, what I cam up with is this:
- Hash[CGI::parse(request.query_string).map{|k,v| [k,v]}].values.each do |value|
- valuename = value[0]
- employees = Employee.all
- found_employees = employees.where('name LIKE ?', "#{valuename}")
- found_employees.each do |employee|
tr
td = employee.name
First line: gets the whole query string, parses over it to look for the parameters and puts them in a hash, then reads out the key value pairs, which then are arrays. Those arrays' values are then iterated over.
Second line: since the values are still in an array, I ask the arrays to just put out the pure values with value[0], and assign them to valuename.
Folowing lines: Just querying the database to find all employee-names that match a valuename, receive objects so I can further work on them.

My nested cocoon form only accepts the last line of my dynamic table

I am currently creating a nested form using cocoon. I have a table that takes in information for everyone in a household. the form dynamically creates a new line when the user clicks on add new member. The problem that I am having is that when the form is submitted, only the last line of the table is passed through.
I have tried many different things including changing the attributes of the members and also trying to isolate just the household members so I can see what is happening. I have tried giving them unique id's, but that didn't work. It might be something to do with the rails sever or just some bad code that I can't see.
Here is where I am nesting the form
<tbody class="members">
<%= f.fields_for :household_members do |ff|%>
<%= render 'household_member_fields', :f => ff %>
<%end%>
</tbody>
</table>
<%= link_to_add_association 'Add household member', f, :household_members, data: {association_insertion_node: '.members',
association_insertion_method: :append} %>
This is the beginning of the households_member_fields:
<tr class= "nested-fields">
<% f.hidden_field :id%>
<td><% f.label(:name)%>
<%= text_field(:household_members, :name) %></td>
<td><% f.label(:birthdate)%>
<%= date_field(:household_members, :birthdate) %></td>
<td><% f.label(:ssn)%>
This is my controller
def create
addresses = params[:addresses].permit([:county]).to_h
contact_info = params[:contact_info].permit(params[:contact_info].keys).to_h
household = params[:household_type].permit([:house_type]).to_h
household_members = member_params
#workflow = CreatesUser.new(address_info: addresses,
contact: contact_info, household: household)
#workflow.create
redirect_to users_path
end
private
def member_params
params.require(:user).permit(household_members_attributes: [:id, :name, :birthdate, :ssn, :gender, :hispanic, :race, :or_tribe, :education, :_destroy])
end
When I currently submit two or more household members I only ever get one of them like this:
"household_members"=>{"name"=>"Fake2", "birthdate"=>"2019-07-21", "ssn"=>"fake2", "gender"=>"Female", "hispanic"=>"0", "race"=>"Alaska Native", "or_tribe"=>"0", "education"=>"Some college"}, "disablility_assistances"=>{"disabled"=>"0", "homebound"=>"0", "snap"=>"0", "ohp"=>"0", "med_insurance"=>"fake2"}, "veteran_statuses"=>{"veteran"=>"0"}
I am expecting to get multiple of these. If you have any incite into what I am doing wrong I would greatly appreciate it!
Thanks,
Aala95
After looking at what my code is returning a little more it looks like the nested form is being submitted but only with an ID and delete:
"user"=>{"household_members_attributes"=>{"0"=>{"_destroy"=>"false"}, "1"=>{"_destroy"=>"false"}}}
You have to fix your nested items partial: remove the scoped name --now it will be double scoped, this explains why some attributes are in household_members (your scope and blocked by the strong parameters definition) and some in household_members_attributes (the expected scope). Also there is no need to add the hidden field :id (that will be automatically handled by the index in the array).
So write your partial as follows:
<tr class= "nested-fields">
<td><% f.label(:name)%>
<%= text_field(:name) %></td>
<td><% f.label(:birthdate)%>
<%= date_field(:birthdate) %></td>
<td><% f.label(:ssn)%>

Rails has_many nested form No route matches [POST]

I'm getting the above error when I submit my nested form and I'm not sure how to fix it. Here is some info. The exact error is No route matches [POST] "/vehicles/2/vehicle_records/new". The strange thing is that route is present when I run rake routes I see the following entry
/vehicles/:vehicle_id/vehicle_records/new(.:format)
vehicle.rb
class Vehicle < ActiveRecord::Base
has_many :vehicle_records, dependent: :destroy
accepts_nested_attributes_for :vehicle_records
end
vehicle_record.rb
class VehicleRecord < ActiveRecord::Base
belongs_to :vehicle
end
I want the ability to add a vehicle maintenance record linked from the vehicle show view.
views/vehicle/show.html.erb
<h3><b>Vehicle ID:</b> <%= #vehicle.id %></h3>
<p><b>Year:</b> <%= #vehicle.year %></p>
<p><b>Make:</b> <%= #vehicle.make %></p>
<p><b>Model:</b> <%= #vehicle.model %></p>
<p><b>Work Last Performed:</b> <%= #vehicle.make %></p>
<h2>Maintenance Record</h2>
<table>
<tr>
<th>Date Performed</th>
<th>Mileage</th>
<th>Hours</th>
<th>Work Performed</th>
</tr>
<% #vehicle.vehicle_records.each do |vr|%>
<tr>
<td><%= vr.date_performed %></td>
<td><%= vr.mileage %></td>
<td><%= vr.hours %></td>
<td><%= vr.work_performed %></td>
</tr>
<% end %>
</table>
<%= link_to "Add Maintenance Record", new_vehicle_vehicle_record_path(#vehicle) %>
I then link to the vehicle_record new form
views/vehicle_records/new.html.erb
<%= form_for :vehicle_record do |vr| %>
<p>
<%= vr.label :date_performed%><br />
<%= vr.text_field :date_performed%>
</p>
<p>
<%= vr.label :mileage%><br />
<%= vr.text_field :mileage%>
</p>
<p>
<%= vr.label :mileage%><br />
<%= vr.text_field :mileage%>
</p>
<p>
<%= vr.label :hours%><br />
<%= vr.text_field :hours%>
</p>
<p>
<%= vr.label :work_performed%><br />
<%= vr.text_area :work_performed%>
</p>
<p><%= vr.submit "Create Record" %></p>
My vehicle records controller is as follows
vehicle_records_controller.rb
class VehicleRecordsController < ApplicationController
def new
#vehicle_record = VehicleRecord.new
end
def create
#vehicle_record = VehicleRecord.new(params[:vehicle_record])
if #vehicle_record.save
redirect_to vehicles_path
else
flash[:notice] = "Not Saved"
end
end
end
routes.rb
resources :vehicles do
resources :vehicle_records
end
Update: w/o nested forms
So I hear you only want to only edit the vehicle_report without touching the vehicle. Since you already have your vehicle_report as nested resource, you can modify your controller as follows:
class VehicleRecordController < ApplicationController
...
def new
#vehicle = Vehicle.find params[:vehicle_id]
#vehicle_record = #vehicle.vehicle_records.build
end
...
end
and the content of create method is fairly easy. Then you have to change your form to:
<%= form_for #vehicle_record do |v| %>
...
<%= # vehicle record attributes %>
...
<%= v.submit "Submit" %>
And you are good to go!
Original answer: w/ nested forms
First of all, I don't see you using any nested forms. Second, I don't see how your vehicle_record form gets to know to which vehicle it actually belongs (see your controller). In my opinion the simplest way would be to allow adding and removing of vehicle_records directly in vehicles/:id/edit or vehicles/new using nested forms:
<%= form_for #vehicle do |v| %>
...
<%= # vehicle attributes here %>
...
<%= v.fields_for :vehicle_records do |vr| %>
...
<%= # vehicle record attributes %>
...
<%= v.submit "Submit" %>
For existing vehicles the fields_for will render list of all existing vehicle_records and for new ones your can have the following code in your controller to make fields_for render for example three empty vehicle_records:
class VehicleController < ApplicationController
...
def new
#vehicle = new Vehicle
3.times { #vehicle.vehicle_records.build }
end
...
end
If you want to always have a number of free vehicle_records every time you edit an existing vehicle you can use a similar approach for your edit and reuse the aforementioned form:
class VehicleController < ApplicationController
...
def edit
#vehicle = Vehicle.find params[:id]
# Add one blank vehicle record to existing ones
# This is also rendered by fields_for
#vehicle.vehicle_records.build
end
...
end

Create Parent with existing children

Does anyone know how to create a new parent with existing children?
class Order < ActiveRecord::Base
has_many :jobs
accepts_nested_attributes_for :jobs
end
class Job < ActiveRecord::Base
belongs_to :quote
belongs_to :order
end
With nested variables, I can build one or more jobs (children) with a new order (parent) and the create method then automatically creates the new parent with it's new children. What I have though, is a group of existing children (jobs that were created in a quote), and I want to include them in a new parent form. When hitting submit to create the new order, I want it to create the new parent, and update the selected children with that new parent ID.
I have messy code that will do this in the create method by saving a new order (parent), then updating each of the jobs (children) with the new order_id, but is there any way to do this automatically?
The actual code is complex, so here's the crux of it.
Let's assume there is a quote with several jobs, therefore the quote_id variable in all these jobs point back to the quote and #quote.jobs = array of these jobs.
Here's what I have now:
In the Order model I added:
attr_accessor :quote_id
In the Order controller:
def new quote_id
#quote = Quote.find(quote_id)
#order = Order.new
and in the view
<%= simple_form_for #order, :html => { :class => 'form-horizontal' } do |f| %>
<%= f.input :quote_id, value: #quote.id, as: :hidden %>
<%= f.input :account_number %>
...
<table>
<thead><tr> ... column headings here ... </tr></thead>
<tbody>
<% #quote.jobs.each do |q| %>
<tr>
<td><%= q.description %></td>
<td><%= q.price %></td>
</tr>
<% end %>
<tr><%= ... add a row with the total price ... %></tr>
</tbody>
</table>
<%= f.button :submit, "Place My Order" %>
<% end $>
Then in the Order controller
def create
...
if #order.save
Quote.find(params[quote_id]).jobs.each do |job|
job.update(order_id: #order.id)
end
end
...
What I would prefer is for the job parameters to be handled as nested objects in the form so they can be automatically updated on an Order create with the order.id and perhaps a couple of other parameters. I've tried (in the controller)
#order = Order.new
#quote.jobs.each do |job|
#order.jobs << job
end
but they're not showing up in a form with the usual nested format and if that worked, I don't know if they would actually be updated.
Since an Order accepts nested attributes for a Job, I suppose you could just manually create and populate the nested jobs fields - as hidden fields - when you build your order form.
<% #quote.jobs.each_with_index do |job, index| %>
<input type='hidden' name='order[jobs_attributes][<% index %>][FIELDNAME]' id='order_jobs_attributes_<% index %>_FIELDNAME' value='<% job.something %>' />
<% end %>
I'm sure there's a cleaner way with simple_form/form helpers to use = f.input to build the hidden fields but I was admittedly being lazy above!

Rails Form element to convey two values

I have a form that takes bookings for an event for people. The form displays events vertically, and a name & checkbox for each of the possible people next to each event.
How should I best convey the two pieces of information that i need per checkbox? that is, the event_id and the person_id
I'm not totally sure wether I got you right. This is the model I assume you're talking about:
# event.rb
class Event
has_many :people
scope :possible_people, -> { #whatever .. }
end
# person.rb
class Person
belongs_to :event
end
# events_controller.rb
class EventsController
def index
#events = Event.all
end
end
And this might be a possible solution to change an events relation to people:
# index.html.erb
<ul id="events">
<% #events.each do |event| %>
<li class="event">
<%= form_for #event do |form| %>
<% event.possible_people.each do |person| %>
<%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %>
<% end %>
<%= f.submit_tag 'Save Event' %>
<% end %>
</li>
<% end %>
</ul>
The important part is <%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %> where you actually change the the relation of a specific person to the event.
Good luck ;)
Well, you can try out something like below line, I am assuming you have a multiselect checkboxes and i am passing a Hash of event_id => plate_id as value to checkbox.
<%= check_box_tag 'booking[event_people_ids][]', {booking.event_id => booking.plate_id} %>
You will get the value in params as:
booking => {event_people_ids =>["{"72"=>"3"}}
I ended up doing this:
<%= check_box_tag "booking[]", "#{event.id}-#{person.id}" %>
and then in then to process them:
params[:booking].each do |booking|
booking = booking.split('-')
a = {
:booking_id => #booking.id,
:person_id => booking[1],
:event_id => booking[0]
}
Appointment.create(a)
end
I was hoping for a more railish way to do it but this works.

Categories

Resources